1require "net/ftp"
2require "test/unit"
3require "ostruct"
4require "stringio"
5
6class FTPTest < Test::Unit::TestCase
7  SERVER_ADDR = "127.0.0.1"
8
9  def setup
10    @thread = nil
11  end
12
13  def teardown
14    if @thread
15      @thread.join
16    end
17  end
18
19  def test_not_connected
20    ftp = Net::FTP.new
21    assert_raise(Net::FTPConnectionError) do
22      ftp.quit
23    end
24  end
25
26  def test_connect_fail
27    server = create_ftp_server { |sock|
28      sock.print("421 Service not available, closing control connection.\r\n")
29    }
30    begin
31      ftp = Net::FTP.new
32      assert_raise(Net::FTPTempError){ ftp.connect(SERVER_ADDR, server.port) }
33    ensure
34      ftp.close if ftp
35      server.close
36    end
37  end
38
39  def test_parse227
40    ftp = Net::FTP.new
41    host, port = ftp.send(:parse227, "227 Entering Passive Mode (192,168,0,1,12,34)")
42    assert_equal("192.168.0.1", host)
43    assert_equal(3106, port)
44    assert_raise(Net::FTPReplyError) do
45      ftp.send(:parse227, "500 Syntax error")
46    end
47    assert_raise(Net::FTPProtoError) do
48      ftp.send(:parse227, "227 Entering Passive Mode")
49    end
50    assert_raise(Net::FTPProtoError) do
51      ftp.send(:parse227, "227 Entering Passive Mode (192,168,0,1,12,34,56)")
52    end
53    assert_raise(Net::FTPProtoError) do
54      ftp.send(:parse227, "227 Entering Passive Mode (192,168,0,1)")
55    end
56    assert_raise(Net::FTPProtoError) do
57      ftp.send(:parse227, "227 ) foo bar (")
58    end
59  end
60
61  def test_parse228
62    ftp = Net::FTP.new
63    host, port = ftp.send(:parse228, "228 Entering Long Passive Mode (4,4,192,168,0,1,2,12,34)")
64    assert_equal("192.168.0.1", host)
65    assert_equal(3106, port)
66    host, port = ftp.send(:parse228, "228 Entering Long Passive Mode (6,16,16,128,0,0,0,0,0,0,0,8,8,0,32,12,65,122,2,12,34)")
67    assert_equal("1080:0000:0000:0000:0008:0800:200c:417a", host)
68    assert_equal(3106, port)
69    assert_raise(Net::FTPReplyError) do
70      ftp.send(:parse228, "500 Syntax error")
71    end
72    assert_raise(Net::FTPProtoError) do
73      ftp.send(:parse228, "228 Entering Passive Mode")
74    end
75    assert_raise(Net::FTPProtoError) do
76      ftp.send(:parse228, "228 Entering Long Passive Mode (6,4,192,168,0,1,2,12,34)")
77    end
78    assert_raise(Net::FTPProtoError) do
79      ftp.send(:parse228, "228 Entering Long Passive Mode (4,4,192,168,0,1,3,12,34,56)")
80    end
81    assert_raise(Net::FTPProtoError) do
82      ftp.send(:parse228, "228 Entering Long Passive Mode (4,16,16,128,0,0,0,0,0,0,0,8,8,0,32,12,65,122,2,12,34)")
83    end
84    assert_raise(Net::FTPProtoError) do
85      ftp.send(:parse228, "228 Entering Long Passive Mode (6,16,16,128,0,0,0,0,0,0,0,8,8,0,32,12,65,122,3,12,34,56)")
86    end
87    assert_raise(Net::FTPProtoError) do
88      ftp.send(:parse228, "228 Entering Long Passive Mode (6,16,16,128,0,0,0,0,0,0,0,8,8,0,32,12,65,122,2,12,34,56)")
89    end
90    assert_raise(Net::FTPProtoError) do
91      ftp.send(:parse227, "227 ) foo bar (")
92    end
93  end
94
95  def test_parse229
96    ftp = Net::FTP.new
97    sock = OpenStruct.new
98    sock.peeraddr = [nil, nil, nil, "1080:0000:0000:0000:0008:0800:200c:417a"]
99    ftp.instance_variable_set(:@sock, sock)
100    host, port = ftp.send(:parse229, "229 Entering Passive Mode (|||3106|)")
101    assert_equal("1080:0000:0000:0000:0008:0800:200c:417a", host)
102    assert_equal(3106, port)
103    host, port = ftp.send(:parse229, "229 Entering Passive Mode (!!!3106!)")
104    assert_equal("1080:0000:0000:0000:0008:0800:200c:417a", host)
105    assert_equal(3106, port)
106    host, port = ftp.send(:parse229, "229 Entering Passive Mode (~~~3106~)")
107    assert_equal("1080:0000:0000:0000:0008:0800:200c:417a", host)
108    assert_equal(3106, port)
109    assert_raise(Net::FTPReplyError) do
110      ftp.send(:parse229, "500 Syntax error")
111    end
112    assert_raise(Net::FTPProtoError) do
113      ftp.send(:parse229, "229 Entering Passive Mode")
114    end
115    assert_raise(Net::FTPProtoError) do
116      ftp.send(:parse229, "229 Entering Passive Mode (|!!3106!)")
117    end
118    assert_raise(Net::FTPProtoError) do
119      ftp.send(:parse229, "229 Entering Passive Mode (   3106 )")
120    end
121    assert_raise(Net::FTPProtoError) do
122      ftp.send(:parse229, "229 Entering Passive Mode (\x7f\x7f\x7f3106\x7f)")
123    end
124    assert_raise(Net::FTPProtoError) do
125      ftp.send(:parse229, "229 ) foo bar (")
126    end
127  end
128
129  def test_parse_pasv_port
130    ftp = Net::FTP.new
131    assert_equal(12, ftp.send(:parse_pasv_port, "12"))
132    assert_equal(3106, ftp.send(:parse_pasv_port, "12,34"))
133    assert_equal(795192, ftp.send(:parse_pasv_port, "12,34,56"))
134    assert_equal(203569230, ftp.send(:parse_pasv_port, "12,34,56,78"))
135  end
136
137  def test_login
138    commands = []
139    server = create_ftp_server { |sock|
140      sock.print("220 (test_ftp).\r\n")
141      commands.push(sock.gets)
142      sock.print("331 Please specify the password.\r\n")
143      commands.push(sock.gets)
144      sock.print("230 Login successful.\r\n")
145      commands.push(sock.gets)
146      sock.print("200 Switching to Binary mode.\r\n")
147    }
148    begin
149      begin
150        ftp = Net::FTP.new
151        ftp.connect(SERVER_ADDR, server.port)
152        ftp.login
153        assert_match(/\AUSER /, commands.shift)
154        assert_match(/\APASS /, commands.shift)
155        assert_equal("TYPE I\r\n", commands.shift)
156        assert_equal(nil, commands.shift)
157      ensure
158        ftp.close if ftp
159      end
160    ensure
161      server.close
162    end
163  end
164
165  def test_login_fail1
166    commands = []
167    server = create_ftp_server { |sock|
168      sock.print("220 (test_ftp).\r\n")
169      commands.push(sock.gets)
170      sock.print("502 Command not implemented.\r\n")
171    }
172    begin
173      begin
174        ftp = Net::FTP.new
175        ftp.connect(SERVER_ADDR, server.port)
176        assert_raise(Net::FTPPermError){ ftp.login }
177      ensure
178        ftp.close if ftp
179      end
180    ensure
181      server.close
182    end
183  end
184
185  def test_login_fail2
186    commands = []
187    server = create_ftp_server { |sock|
188      sock.print("220 (test_ftp).\r\n")
189      commands.push(sock.gets)
190      sock.print("331 Please specify the password.\r\n")
191      commands.push(sock.gets)
192      sock.print("530 Not logged in.\r\n")
193    }
194    begin
195      begin
196        ftp = Net::FTP.new
197        ftp.connect(SERVER_ADDR, server.port)
198        assert_raise(Net::FTPPermError){ ftp.login }
199      ensure
200        ftp.close if ftp
201      end
202    ensure
203      server.close
204    end
205  end
206
207  # TODO: How can we test open_timeout?  sleep before accept cannot delay
208  # connections.
209  def _test_open_timeout_exceeded
210    commands = []
211    server = create_ftp_server(0.2) { |sock|
212      sock.print("220 (test_ftp).\r\n")
213      commands.push(sock.gets)
214      sock.print("331 Please specify the password.\r\n")
215      commands.push(sock.gets)
216      sock.print("230 Login successful.\r\n")
217      commands.push(sock.gets)
218      sock.print("200 Switching to Binary mode.\r\n")
219    }
220    begin
221      begin
222        ftp = Net::FTP.new
223        ftp.open_timeout = 0.1
224        ftp.connect(SERVER_ADDR, server.port)
225        assert_raise(Net::OpenTimeout) do
226          ftp.login
227        end
228        assert_match(/\AUSER /, commands.shift)
229        assert_match(/\APASS /, commands.shift)
230        assert_equal(nil, commands.shift)
231      ensure
232        ftp.close if ftp
233      end
234    ensure
235      server.close
236    end
237  end
238
239  def test_read_timeout_exceeded
240    commands = []
241    server = create_ftp_server { |sock|
242      sock.print("220 (test_ftp).\r\n")
243      commands.push(sock.gets)
244      sleep(0.1)
245      sock.print("331 Please specify the password.\r\n")
246      commands.push(sock.gets)
247      sleep(0.3)
248      sock.print("230 Login successful.\r\n")
249      commands.push(sock.gets)
250      sleep(0.1)
251      sock.print("200 Switching to Binary mode.\r\n")
252    }
253    begin
254      begin
255        ftp = Net::FTP.new
256        ftp.read_timeout = 0.2
257        ftp.connect(SERVER_ADDR, server.port)
258        assert_raise(Net::ReadTimeout) do
259          ftp.login
260        end
261        assert_match(/\AUSER /, commands.shift)
262        assert_match(/\APASS /, commands.shift)
263        assert_equal(nil, commands.shift)
264      ensure
265        ftp.close if ftp
266      end
267    ensure
268      server.close
269    end
270  end
271
272  def test_read_timeout_not_exceeded
273    commands = []
274    server = create_ftp_server { |sock|
275      sock.print("220 (test_ftp).\r\n")
276      commands.push(sock.gets)
277      sleep(0.1)
278      sock.print("331 Please specify the password.\r\n")
279      commands.push(sock.gets)
280      sleep(0.1)
281      sock.print("230 Login successful.\r\n")
282      commands.push(sock.gets)
283      sleep(0.1)
284      sock.print("200 Switching to Binary mode.\r\n")
285    }
286    begin
287      begin
288        ftp = Net::FTP.new
289        ftp.read_timeout = 0.2
290        ftp.connect(SERVER_ADDR, server.port)
291        ftp.login
292        assert_match(/\AUSER /, commands.shift)
293        assert_match(/\APASS /, commands.shift)
294        assert_equal("TYPE I\r\n", commands.shift)
295        assert_equal(nil, commands.shift)
296      ensure
297        ftp.close
298        assert_equal(0.2, ftp.read_timeout)
299      end
300    ensure
301      server.close
302    end
303  end
304
305  def test_list_read_timeout_exceeded
306    commands = []
307    list_lines = [
308      "-rw-r--r--    1 0        0               0 Mar 30 11:22 foo.txt",
309      "-rw-r--r--    1 0        0               0 Mar 30 11:22 bar.txt",
310      "-rw-r--r--    1 0        0               0 Mar 30 11:22 baz.txt"
311    ]
312    server = create_ftp_server { |sock|
313      sock.print("220 (test_ftp).\r\n")
314      commands.push(sock.gets)
315      sock.print("331 Please specify the password.\r\n")
316      commands.push(sock.gets)
317      sock.print("230 Login successful.\r\n")
318      commands.push(sock.gets)
319      sock.print("200 Switching to Binary mode.\r\n")
320      commands.push(sock.gets)
321      sock.print("200 Switching to ASCII mode.\r\n")
322      line = sock.gets
323      commands.push(line)
324      port_args = line.slice(/\APORT (.*)/, 1).split(/,/)
325      host = port_args[0, 4].join(".")
326      port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y}
327      sock.print("200 PORT command successful.\r\n")
328      commands.push(sock.gets)
329      sock.print("150 Here comes the directory listing.\r\n")
330      begin
331        conn = TCPSocket.new(host, port)
332        list_lines.each_with_index do |l, i|
333          if i == 1
334            sleep(0.5)
335          else
336            sleep(0.1)
337          end
338          conn.print(l, "\r\n")
339        end
340      rescue Errno::EPIPE
341      ensure
342        assert_nil($!)
343        conn.close
344      end
345      sock.print("226 Directory send OK.\r\n")
346    }
347    begin
348      begin
349        ftp = Net::FTP.new
350        ftp.read_timeout = 0.2
351        ftp.connect(SERVER_ADDR, server.port)
352        ftp.login
353        assert_match(/\AUSER /, commands.shift)
354        assert_match(/\APASS /, commands.shift)
355        assert_equal("TYPE I\r\n", commands.shift)
356        assert_raise(Net::ReadTimeout) do
357          ftp.list
358        end
359        assert_equal("TYPE A\r\n", commands.shift)
360        assert_match(/\APORT /, commands.shift)
361        assert_equal("LIST\r\n", commands.shift)
362        assert_equal(nil, commands.shift)
363      ensure
364        ftp.close if ftp
365      end
366    ensure
367      server.close
368    end
369  end
370
371  def test_list_read_timeout_not_exceeded
372    commands = []
373    list_lines = [
374      "-rw-r--r--    1 0        0               0 Mar 30 11:22 foo.txt",
375      "-rw-r--r--    1 0        0               0 Mar 30 11:22 bar.txt",
376      "-rw-r--r--    1 0        0               0 Mar 30 11:22 baz.txt"
377    ]
378    server = create_ftp_server { |sock|
379      sock.print("220 (test_ftp).\r\n")
380      commands.push(sock.gets)
381      sock.print("331 Please specify the password.\r\n")
382      commands.push(sock.gets)
383      sock.print("230 Login successful.\r\n")
384      commands.push(sock.gets)
385      sock.print("200 Switching to Binary mode.\r\n")
386      commands.push(sock.gets)
387      sock.print("200 Switching to ASCII mode.\r\n")
388      line = sock.gets
389      commands.push(line)
390      port_args = line.slice(/\APORT (.*)/, 1).split(/,/)
391      host = port_args[0, 4].join(".")
392      port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y}
393      sock.print("200 PORT command successful.\r\n")
394      commands.push(sock.gets)
395      sock.print("150 Here comes the directory listing.\r\n")
396      conn = TCPSocket.new(host, port)
397      list_lines.each do |l|
398        sleep(0.1)
399        conn.print(l, "\r\n")
400      end
401      conn.close
402      sock.print("226 Directory send OK.\r\n")
403      commands.push(sock.gets)
404      sock.print("200 Switching to Binary mode.\r\n")
405    }
406    begin
407      begin
408        ftp = Net::FTP.new
409        ftp.read_timeout = 0.2
410        ftp.connect(SERVER_ADDR, server.port)
411        ftp.login
412        assert_match(/\AUSER /, commands.shift)
413        assert_match(/\APASS /, commands.shift)
414        assert_equal("TYPE I\r\n", commands.shift)
415        assert_equal(list_lines, ftp.list)
416        assert_equal("TYPE A\r\n", commands.shift)
417        assert_match(/\APORT /, commands.shift)
418        assert_equal("LIST\r\n", commands.shift)
419        assert_equal("TYPE I\r\n", commands.shift)
420        assert_equal(nil, commands.shift)
421      ensure
422        ftp.close if ftp
423      end
424    ensure
425      server.close
426    end
427  end
428
429  def test_list_fail
430    commands = []
431    list_lines = [
432      "-rw-r--r--    1 0        0               0 Mar 30 11:22 foo.txt",
433      "-rw-r--r--    1 0        0               0 Mar 30 11:22 bar.txt",
434      "-rw-r--r--    1 0        0               0 Mar 30 11:22 baz.txt"
435    ]
436    server = create_ftp_server { |sock|
437      sock.print("220 (test_ftp).\r\n")
438      commands.push(sock.gets)
439      sock.print("331 Please specify the password.\r\n")
440      commands.push(sock.gets)
441      sock.print("230 Login successful.\r\n")
442      commands.push(sock.gets)
443      sock.print("200 Switching to Binary mode.\r\n")
444      commands.push(sock.gets)
445      sock.print("200 Switching to ASCII mode.\r\n")
446      line = sock.gets
447      commands.push(line)
448      port_args = line.slice(/\APORT (.*)/, 1).split(/,/)
449      host = port_args[0, 4].join(".")
450      port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y}
451      sock.print("200 PORT command successful.\r\n")
452      commands.push(sock.gets)
453      sock.print("553 Requested action not taken.\r\n")
454      commands.push(sock.gets)
455      sock.print("200 Switching to Binary mode.\r\n")
456    }
457    begin
458      begin
459        ftp = Net::FTP.new
460        ftp.read_timeout = 0.2
461        ftp.connect(SERVER_ADDR, server.port)
462        ftp.login
463        assert_match(/\AUSER /, commands.shift)
464        assert_match(/\APASS /, commands.shift)
465        assert_equal("TYPE I\r\n", commands.shift)
466        assert_raise(Net::FTPPermError){ ftp.list }
467        assert_equal("TYPE A\r\n", commands.shift)
468        assert_match(/\APORT /, commands.shift)
469        assert_equal("LIST\r\n", commands.shift)
470        assert_equal("TYPE I\r\n", commands.shift)
471        assert_equal(nil, commands.shift)
472      ensure
473        ftp.close if ftp
474      end
475    ensure
476      server.close
477    end
478  end
479
480  def test_retrbinary_read_timeout_exceeded
481    commands = []
482    binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3
483    server = create_ftp_server { |sock|
484      sock.print("220 (test_ftp).\r\n")
485      commands.push(sock.gets)
486      sock.print("331 Please specify the password.\r\n")
487      commands.push(sock.gets)
488      sock.print("230 Login successful.\r\n")
489      commands.push(sock.gets)
490      sock.print("200 Switching to Binary mode.\r\n")
491      line = sock.gets
492      commands.push(line)
493      port_args = line.slice(/\APORT (.*)/, 1).split(/,/)
494      host = port_args[0, 4].join(".")
495      port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y}
496      sock.print("200 PORT command successful.\r\n")
497      commands.push(sock.gets)
498      sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n")
499      conn = TCPSocket.new(host, port)
500      sleep(0.1)
501      conn.print(binary_data[0,1024])
502      sleep(0.5)
503      conn.print(binary_data[1024, 1024]) rescue nil # may raise EPIPE or something
504      conn.close
505      sock.print("226 Transfer complete.\r\n")
506    }
507    begin
508      begin
509        ftp = Net::FTP.new
510        ftp.read_timeout = 0.2
511        ftp.connect(SERVER_ADDR, server.port)
512        ftp.login
513        assert_match(/\AUSER /, commands.shift)
514        assert_match(/\APASS /, commands.shift)
515        assert_equal("TYPE I\r\n", commands.shift)
516        buf = ""
517        assert_raise(Net::ReadTimeout) do
518          ftp.retrbinary("RETR foo", 1024) do |s|
519            buf << s
520          end
521        end
522        assert_equal(1024, buf.bytesize)
523        assert_equal(binary_data[0, 1024], buf)
524        assert_match(/\APORT /, commands.shift)
525        assert_equal("RETR foo\r\n", commands.shift)
526        assert_equal(nil, commands.shift)
527      ensure
528        ftp.close unless ftp.closed?
529      end
530    ensure
531      server.close
532    end
533  end
534
535  def test_retrbinary_read_timeout_not_exceeded
536    commands = []
537    binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3
538    server = create_ftp_server { |sock|
539      sock.print("220 (test_ftp).\r\n")
540      commands.push(sock.gets)
541      sock.print("331 Please specify the password.\r\n")
542      commands.push(sock.gets)
543      sock.print("230 Login successful.\r\n")
544      commands.push(sock.gets)
545      sock.print("200 Switching to Binary mode.\r\n")
546      line = sock.gets
547      commands.push(line)
548      port_args = line.slice(/\APORT (.*)/, 1).split(/,/)
549      host = port_args[0, 4].join(".")
550      port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y}
551      sock.print("200 PORT command successful.\r\n")
552      commands.push(sock.gets)
553      sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n")
554      conn = TCPSocket.new(host, port)
555      binary_data.scan(/.{1,1024}/nm) do |s|
556        sleep(0.1)
557        conn.print(s)
558      end
559      conn.shutdown(Socket::SHUT_WR)
560      conn.read
561      conn.close
562      sock.print("226 Transfer complete.\r\n")
563    }
564    begin
565      begin
566        ftp = Net::FTP.new
567        ftp.read_timeout = 0.2
568        ftp.connect(SERVER_ADDR, server.port)
569        ftp.login
570        assert_match(/\AUSER /, commands.shift)
571        assert_match(/\APASS /, commands.shift)
572        assert_equal("TYPE I\r\n", commands.shift)
573        buf = ""
574        ftp.retrbinary("RETR foo", 1024) do |s|
575          buf << s
576        end
577        assert_equal(binary_data.bytesize, buf.bytesize)
578        assert_equal(binary_data, buf)
579        assert_match(/\APORT /, commands.shift)
580        assert_equal("RETR foo\r\n", commands.shift)
581        assert_equal(nil, commands.shift)
582      ensure
583        ftp.close if ftp
584      end
585    ensure
586      server.close
587    end
588  end
589
590  def test_retrbinary_fail
591    commands = []
592    binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3
593    server = create_ftp_server { |sock|
594      sock.print("220 (test_ftp).\r\n")
595      commands.push(sock.gets)
596      sock.print("331 Please specify the password.\r\n")
597      commands.push(sock.gets)
598      sock.print("230 Login successful.\r\n")
599      commands.push(sock.gets)
600      sock.print("200 Switching to Binary mode.\r\n")
601      line = sock.gets
602      commands.push(line)
603      port_args = line.slice(/\APORT (.*)/, 1).split(/,/)
604      host = port_args[0, 4].join(".")
605      port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y}
606      sock.print("200 PORT command successful.\r\n")
607      commands.push(sock.gets)
608      sock.print("550 Requested action not taken.\r\n")
609    }
610    begin
611      begin
612        ftp = Net::FTP.new
613        ftp.read_timeout = 0.2
614        ftp.connect(SERVER_ADDR, server.port)
615        ftp.login
616        assert_match(/\AUSER /, commands.shift)
617        assert_match(/\APASS /, commands.shift)
618        assert_equal("TYPE I\r\n", commands.shift)
619        assert_raise(Net::FTPPermError){ ftp.retrbinary("RETR foo", 1024) }
620        assert_match(/\APORT /, commands.shift)
621        assert_equal("RETR foo\r\n", commands.shift)
622        assert_equal(nil, commands.shift)
623      ensure
624        ftp.close if ftp
625      end
626    ensure
627      server.close
628    end
629  end
630
631  def test_storbinary
632    commands = []
633    binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3
634    stored_data = nil
635    server = create_ftp_server { |sock|
636      sock.print("220 (test_ftp).\r\n")
637      commands.push(sock.gets)
638      sock.print("331 Please specify the password.\r\n")
639      commands.push(sock.gets)
640      sock.print("230 Login successful.\r\n")
641      commands.push(sock.gets)
642      sock.print("200 Switching to Binary mode.\r\n")
643      line = sock.gets
644      commands.push(line)
645      port_args = line.slice(/\APORT (.*)/, 1).split(/,/)
646      host = port_args[0, 4].join(".")
647      port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y}
648      sock.print("200 PORT command successful.\r\n")
649      commands.push(sock.gets)
650      sock.print("150 Opening BINARY mode data connection for foo\r\n")
651      conn = TCPSocket.new(host, port)
652      stored_data = conn.read
653      conn.close
654      sock.print("226 Transfer complete.\r\n")
655    }
656    begin
657      begin
658        ftp = Net::FTP.new
659        ftp.read_timeout = 0.2
660        ftp.connect(SERVER_ADDR, server.port)
661        ftp.login
662        assert_match(/\AUSER /, commands.shift)
663        assert_match(/\APASS /, commands.shift)
664        assert_equal("TYPE I\r\n", commands.shift)
665        ftp.storbinary("STOR foo", StringIO.new(binary_data), 1024)
666        assert_equal(binary_data, stored_data)
667        assert_match(/\APORT /, commands.shift)
668        assert_equal("STOR foo\r\n", commands.shift)
669        assert_equal(nil, commands.shift)
670      ensure
671        ftp.close if ftp
672      end
673    ensure
674      server.close
675    end
676  end
677
678  def test_storbinary_fail
679    commands = []
680    binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3
681    stored_data = nil
682    server = create_ftp_server { |sock|
683      sock.print("220 (test_ftp).\r\n")
684      commands.push(sock.gets)
685      sock.print("331 Please specify the password.\r\n")
686      commands.push(sock.gets)
687      sock.print("230 Login successful.\r\n")
688      commands.push(sock.gets)
689      sock.print("200 Switching to Binary mode.\r\n")
690      line = sock.gets
691      commands.push(line)
692      port_args = line.slice(/\APORT (.*)/, 1).split(/,/)
693      host = port_args[0, 4].join(".")
694      port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y}
695      sock.print("200 PORT command successful.\r\n")
696      commands.push(sock.gets)
697      sock.print("452 Requested file action aborted.\r\n")
698    }
699    begin
700      begin
701        ftp = Net::FTP.new
702        ftp.read_timeout = 0.2
703        ftp.connect(SERVER_ADDR, server.port)
704        ftp.login
705        assert_match(/\AUSER /, commands.shift)
706        assert_match(/\APASS /, commands.shift)
707        assert_equal("TYPE I\r\n", commands.shift)
708        assert_raise(Net::FTPTempError){ ftp.storbinary("STOR foo", StringIO.new(binary_data), 1024) }
709        assert_match(/\APORT /, commands.shift)
710        assert_equal("STOR foo\r\n", commands.shift)
711        assert_equal(nil, commands.shift)
712      ensure
713        ftp.close if ftp
714      end
715    ensure
716      server.close
717    end
718  end
719
720  def test_abort
721    commands = []
722    server = create_ftp_server { |sock|
723      sock.print("220 (test_ftp).\r\n")
724      commands.push(sock.gets)
725      sock.print("331 Please specify the password.\r\n")
726      commands.push(sock.gets)
727      sock.print("230 Login successful.\r\n")
728      commands.push(sock.gets)
729      sock.print("200 Switching to Binary mode.\r\n")
730      commands.push(sock.recv(1024))
731      sock.print("225 No transfer to ABOR.\r\n")
732    }
733    begin
734      begin
735        ftp = Net::FTP.new
736        #ftp.read_timeout = 0.2
737        ftp.connect(SERVER_ADDR, server.port)
738        ftp.login
739        assert_match(/\AUSER /, commands.shift)
740        assert_match(/\APASS /, commands.shift)
741        assert_equal("TYPE I\r\n", commands.shift)
742        ftp.abort
743        assert_equal("ABOR\r", commands.shift)
744        assert_equal(nil, commands.shift)
745      ensure
746        ftp.close if ftp
747      end
748    ensure
749      server.close
750    end
751  end
752
753  def test_status
754    commands = []
755    server = create_ftp_server { |sock|
756      sock.print("220 (test_ftp).\r\n")
757      commands.push(sock.gets)
758      sock.print("331 Please specify the password.\r\n")
759      commands.push(sock.gets)
760      sock.print("230 Login successful.\r\n")
761      commands.push(sock.gets)
762      sock.print("200 Switching to Binary mode.\r\n")
763      commands.push(sock.recv(1024))
764      sock.print("211 End of status\r\n")
765    }
766    begin
767      begin
768        ftp = Net::FTP.new
769        ftp.read_timeout = 0.2
770        ftp.connect(SERVER_ADDR, server.port)
771        ftp.login
772        assert_match(/\AUSER /, commands.shift)
773        assert_match(/\APASS /, commands.shift)
774        assert_equal("TYPE I\r\n", commands.shift)
775        ftp.status
776        assert_equal("STAT\r", commands.shift)
777        assert_equal(nil, commands.shift)
778      ensure
779        ftp.close if ftp
780      end
781    ensure
782      server.close
783    end
784  end
785
786  private
787
788
789  def create_ftp_server(sleep_time = nil)
790    server = TCPServer.new(SERVER_ADDR, 0)
791    @thread = Thread.start do
792      begin
793        if sleep_time
794          sleep(sleep_time)
795        end
796        sock = server.accept
797        begin
798          yield(sock)
799          sock.shutdown(Socket::SHUT_WR)
800          sock.read_timeout = 1
801          sock.read unless sock.eof?
802        ensure
803          sock.close
804        end
805      rescue
806      end
807    end
808    def server.port
809      addr[1]
810    end
811    return server
812  end
813end
814