# frozen_string_literal: true require "net/ftp" require "test/unit" require "ostruct" require "stringio" require "tempfile" class FTPTest < Test::Unit::TestCase SERVER_ADDR = "127.0.0.1" CA_FILE = File.expand_path("../imap/cacert.pem", __dir__) SERVER_KEY = File.expand_path("../imap/server.key", __dir__) SERVER_CERT = File.expand_path("../imap/server.crt", __dir__) def setup @thread = nil @default_passive = Net::FTP.default_passive Net::FTP.default_passive = false end def teardown Net::FTP.default_passive = @default_passive if @thread @thread.join end end def test_not_connected ftp = Net::FTP.new assert_raise(Net::FTPConnectionError) do ftp.quit end end def test_closed_when_not_connected ftp = Net::FTP.new assert_equal(true, ftp.closed?) assert_nothing_raised(Net::FTPConnectionError) do ftp.close end end def test_connect_fail server = create_ftp_server { |sock| sock.print("421 Service not available, closing control connection.\r\n") } begin ftp = Net::FTP.new assert_raise(Net::FTPTempError){ ftp.connect(SERVER_ADDR, server.port) } ensure ftp.close if ftp server.close end end def test_parse227 ftp = Net::FTP.new host, port = ftp.send(:parse227, "227 Entering Passive Mode (192,168,0,1,12,34)") assert_equal("192.168.0.1", host) assert_equal(3106, port) assert_raise(Net::FTPReplyError) do ftp.send(:parse227, "500 Syntax error") end assert_raise(Net::FTPProtoError) do ftp.send(:parse227, "227 Entering Passive Mode") end assert_raise(Net::FTPProtoError) do ftp.send(:parse227, "227 Entering Passive Mode (192,168,0,1,12,34,56)") end assert_raise(Net::FTPProtoError) do ftp.send(:parse227, "227 Entering Passive Mode (192,168,0,1)") end assert_raise(Net::FTPProtoError) do ftp.send(:parse227, "227 ) foo bar (") end end def test_parse228 ftp = Net::FTP.new host, port = ftp.send(:parse228, "228 Entering Long Passive Mode (4,4,192,168,0,1,2,12,34)") assert_equal("192.168.0.1", host) assert_equal(3106, port) 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)") assert_equal("1080:0000:0000:0000:0008:0800:200c:417a", host) assert_equal(3106, port) assert_raise(Net::FTPReplyError) do ftp.send(:parse228, "500 Syntax error") end assert_raise(Net::FTPProtoError) do ftp.send(:parse228, "228 Entering Passive Mode") end assert_raise(Net::FTPProtoError) do ftp.send(:parse228, "228 Entering Long Passive Mode (6,4,192,168,0,1,2,12,34)") end assert_raise(Net::FTPProtoError) do ftp.send(:parse228, "228 Entering Long Passive Mode (4,4,192,168,0,1,3,12,34,56)") end assert_raise(Net::FTPProtoError) do 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)") end assert_raise(Net::FTPProtoError) do 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)") end assert_raise(Net::FTPProtoError) do 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)") end assert_raise(Net::FTPProtoError) do ftp.send(:parse227, "227 ) foo bar (") end end def test_parse229 ftp = Net::FTP.new sock = OpenStruct.new sock.remote_address = OpenStruct.new sock.remote_address.ip_address = "1080:0000:0000:0000:0008:0800:200c:417a" ftp.instance_variable_set(:@sock, sock) host, port = ftp.send(:parse229, "229 Entering Passive Mode (|||3106|)") assert_equal("1080:0000:0000:0000:0008:0800:200c:417a", host) assert_equal(3106, port) host, port = ftp.send(:parse229, "229 Entering Passive Mode (!!!3106!)") assert_equal("1080:0000:0000:0000:0008:0800:200c:417a", host) assert_equal(3106, port) host, port = ftp.send(:parse229, "229 Entering Passive Mode (~~~3106~)") assert_equal("1080:0000:0000:0000:0008:0800:200c:417a", host) assert_equal(3106, port) assert_raise(Net::FTPReplyError) do ftp.send(:parse229, "500 Syntax error") end assert_raise(Net::FTPProtoError) do ftp.send(:parse229, "229 Entering Passive Mode") end assert_raise(Net::FTPProtoError) do ftp.send(:parse229, "229 Entering Passive Mode (|!!3106!)") end assert_raise(Net::FTPProtoError) do ftp.send(:parse229, "229 Entering Passive Mode ( 3106 )") end assert_raise(Net::FTPProtoError) do ftp.send(:parse229, "229 Entering Passive Mode (\x7f\x7f\x7f3106\x7f)") end assert_raise(Net::FTPProtoError) do ftp.send(:parse229, "229 ) foo bar (") end end def test_parse_pasv_port ftp = Net::FTP.new assert_equal(12, ftp.send(:parse_pasv_port, "12")) assert_equal(3106, ftp.send(:parse_pasv_port, "12,34")) assert_equal(795192, ftp.send(:parse_pasv_port, "12,34,56")) assert_equal(203569230, ftp.send(:parse_pasv_port, "12,34,56,78")) end def test_login commands = [] server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") } begin begin ftp = Net::FTP.new ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_login_fail1 commands = [] server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("502 Command not implemented.\r\n") } begin begin ftp = Net::FTP.new ftp.connect(SERVER_ADDR, server.port) assert_raise(Net::FTPPermError){ ftp.login } ensure ftp.close if ftp end ensure server.close end end def test_login_fail2 commands = [] server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("530 Not logged in.\r\n") } begin begin ftp = Net::FTP.new ftp.connect(SERVER_ADDR, server.port) assert_raise(Net::FTPPermError){ ftp.login } ensure ftp.close if ftp end ensure server.close end end def test_implicit_login commands = [] server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("332 Need account for login.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") } begin begin ftp = Net::FTP.new(SERVER_ADDR, port: server.port, user: "foo", passwd: "bar", acct: "baz") assert_equal("USER foo\r\n", commands.shift) assert_equal("PASS bar\r\n", commands.shift) assert_equal("ACCT baz\r\n", commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_s_open commands = [] server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") } begin Net::FTP.open(SERVER_ADDR, port: server.port, user: "anonymous") do end assert_equal("USER anonymous\r\n", commands.shift) assert_equal("PASS anonymous@\r\n", commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_equal(nil, commands.shift) ensure server.close end end # TODO: How can we test open_timeout? sleep before accept cannot delay # connections. def _test_open_timeout_exceeded commands = [] server = create_ftp_server(0.2) { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") } begin begin ftp = Net::FTP.new ftp.open_timeout = 0.1 ftp.connect(SERVER_ADDR, server.port) assert_raise(Net::OpenTimeout) do ftp.login end assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_read_timeout_exceeded commands = [] server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sleep(0.1) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sleep(0.3) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sleep(0.1) sock.print("200 Switching to Binary mode.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) assert_raise(Net::ReadTimeout) do ftp.login end assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_read_timeout_not_exceeded commands = [] server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sleep(0.1) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sleep(0.1) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sleep(0.1) sock.print("200 Switching to Binary mode.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close assert_equal(0.2, ftp.read_timeout) end ensure server.close end end def test_list_read_timeout_exceeded commands = [] list_lines = [ "-rw-r--r-- 1 0 0 0 Mar 30 11:22 foo.txt", "-rw-r--r-- 1 0 0 0 Mar 30 11:22 bar.txt", "-rw-r--r-- 1 0 0 0 Mar 30 11:22 baz.txt" ] server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") commands.push(sock.gets) sock.print("200 Switching to ASCII mode.\r\n") line = sock.gets commands.push(line) port_args = line.slice(/\APORT (.*)/, 1).split(/,/) host = port_args[0, 4].join(".") port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y} sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("150 Here comes the directory listing.\r\n") begin conn = TCPSocket.new(host, port) list_lines.each_with_index do |l, i| if i == 1 sleep(0.5) else sleep(0.1) end conn.print(l, "\r\n") end rescue Errno::EPIPE ensure assert_nil($!) conn.close end sock.print("226 Directory send OK.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_raise(Net::ReadTimeout) do ftp.list end assert_equal("TYPE A\r\n", commands.shift) assert_match(/\APORT /, commands.shift) assert_equal("LIST\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_list_read_timeout_not_exceeded commands = [] list_lines = [ "-rw-r--r-- 1 0 0 0 Mar 30 11:22 foo.txt", "-rw-r--r-- 1 0 0 0 Mar 30 11:22 bar.txt", "-rw-r--r-- 1 0 0 0 Mar 30 11:22 baz.txt" ] server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") commands.push(sock.gets) sock.print("200 Switching to ASCII mode.\r\n") line = sock.gets commands.push(line) port_args = line.slice(/\APORT (.*)/, 1).split(/,/) host = port_args[0, 4].join(".") port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y} sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("150 Here comes the directory listing.\r\n") conn = TCPSocket.new(host, port) list_lines.each do |l| sleep(0.1) conn.print(l, "\r\n") end conn.close sock.print("226 Directory send OK.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_equal(list_lines, ftp.list) assert_equal("TYPE A\r\n", commands.shift) assert_match(/\APORT /, commands.shift) assert_equal("LIST\r\n", commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_list_fail commands = [] server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") commands.push(sock.gets) sock.print("200 Switching to ASCII mode.\r\n") line = sock.gets commands.push(line) sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("553 Requested action not taken.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_raise(Net::FTPPermError){ ftp.list } assert_equal("TYPE A\r\n", commands.shift) assert_match(/\APORT /, commands.shift) assert_equal("LIST\r\n", commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_open_data_port_fail_no_leak commands = [] server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") commands.push(sock.gets) sock.print("200 Switching to ASCII mode.\r\n") line = sock.gets commands.push(line) sock.print("421 Service not available, closing control connection.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_raise(Net::FTPTempError){ ftp.list } assert_equal("TYPE A\r\n", commands.shift) assert_match(/\APORT /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_retrbinary_read_timeout_exceeded commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") line = sock.gets commands.push(line) port_args = line.slice(/\APORT (.*)/, 1).split(/,/) host = port_args[0, 4].join(".") port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y} sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") conn = TCPSocket.new(host, port) sleep(0.1) conn.print(binary_data[0,1024]) sleep(0.5) conn.print(binary_data[1024, 1024]) rescue nil # may raise EPIPE or something conn.close sock.print("226 Transfer complete.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) buf = String.new assert_raise(Net::ReadTimeout) do ftp.retrbinary("RETR foo", 1024) do |s| buf << s end end assert_equal(1024, buf.bytesize) assert_equal(binary_data[0, 1024], buf) assert_match(/\APORT /, commands.shift) assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close unless ftp.closed? end ensure server.close end end def test_retrbinary_read_timeout_not_exceeded commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") line = sock.gets commands.push(line) port_args = line.slice(/\APORT (.*)/, 1).split(/,/) host = port_args[0, 4].join(".") port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y} sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") conn = TCPSocket.new(host, port) binary_data.scan(/.{1,1024}/nm) do |s| sleep(0.1) conn.print(s) end conn.shutdown(Socket::SHUT_WR) conn.read conn.close sock.print("226 Transfer complete.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) buf = String.new ftp.retrbinary("RETR foo", 1024) do |s| buf << s end assert_equal(binary_data.bytesize, buf.bytesize) assert_equal(binary_data, buf) assert_match(/\APORT /, commands.shift) assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_retrbinary_fail commands = [] server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") line = sock.gets commands.push(line) sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("550 Requested action not taken.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_raise(Net::FTPPermError){ ftp.retrbinary("RETR foo", 1024) } assert_match(/\APORT /, commands.shift) assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_getbinaryfile commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") line = sock.gets commands.push(line) port_args = line.slice(/\APORT (.*)/, 1).split(/,/) host = port_args[0, 4].join(".") port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y} sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") conn = TCPSocket.new(host, port) binary_data.scan(/.{1,1024}/nm) do |s| conn.print(s) end conn.shutdown(Socket::SHUT_WR) conn.read conn.close sock.print("226 Transfer complete.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) buf = ftp.getbinaryfile("foo", nil) assert_equal(binary_data, buf) assert_equal(Encoding::ASCII_8BIT, buf.encoding) assert_match(/\APORT /, commands.shift) assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_getbinaryfile_empty commands = [] binary_data = "" server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") line = sock.gets commands.push(line) port_args = line.slice(/\APORT (.*)/, 1).split(/,/) host = port_args[0, 4].join(".") port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y} sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") conn = TCPSocket.new(host, port) conn.shutdown(Socket::SHUT_WR) conn.read conn.close sock.print("226 Transfer complete.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) buf = ftp.getbinaryfile("foo", nil) assert_equal(binary_data, buf) assert_equal(Encoding::ASCII_8BIT, buf.encoding) assert_match(/\APORT /, commands.shift) assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_getbinaryfile_with_filename_and_block commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") line = sock.gets commands.push(line) port_args = line.slice(/\APORT (.*)/, 1).split(/,/) host = port_args[0, 4].join(".") port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y} sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") conn = TCPSocket.new(host, port) binary_data.scan(/.{1,1024}/nm) do |s| conn.print(s) end conn.shutdown(Socket::SHUT_WR) conn.read conn.close sock.print("226 Transfer complete.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) Tempfile.create("foo", external_encoding: "ASCII-8BIT") do |f| f.binmode buf = String.new res = ftp.getbinaryfile("foo", f.path) { |s| buf << s } assert_equal(nil, res) assert_equal(binary_data, buf) assert_equal(Encoding::ASCII_8BIT, buf.encoding) assert_equal(binary_data, f.read) end assert_match(/\APORT /, commands.shift) assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_storbinary commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 stored_data = nil server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") line = sock.gets commands.push(line) port_args = line.slice(/\APORT (.*)/, 1).split(/,/) host = port_args[0, 4].join(".") port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y} sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("150 Opening BINARY mode data connection for foo\r\n") conn = TCPSocket.new(host, port) stored_data = conn.read conn.close sock.print("226 Transfer complete.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) ftp.storbinary("STOR foo", StringIO.new(binary_data), 1024) assert_equal(binary_data, stored_data) assert_match(/\APORT /, commands.shift) assert_equal("STOR foo\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_storbinary_fail commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 server = create_ftp_server { |sock| sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") line = sock.gets commands.push(line) sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("452 Requested file action aborted.\r\n") } begin begin ftp = Net::FTP.new ftp.read_timeout = 0.2 ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) assert_raise(Net::FTPTempError){ ftp.storbinary("STOR foo", StringIO.new(binary_data), 1024) } assert_match(/\APORT /, commands.shift) assert_equal("STOR foo\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close if ftp end ensure server.close end end def test_retrlines commands = [] text_data = < port, :ssl => true) rescue SystemCallError skip $! end end end end def test_tls_with_ca_file assert_nothing_raised do tls_test do |port| begin Net::FTP.new("localhost", :port => port, :ssl => { :ca_file => CA_FILE }) rescue SystemCallError skip $! end end end end def test_tls_verify_none assert_nothing_raised do tls_test do |port| Net::FTP.new(SERVER_ADDR, :port => port, :ssl => { :verify_mode => OpenSSL::SSL::VERIFY_NONE }) end end end def test_tls_post_connection_check assert_raise(OpenSSL::SSL::SSLError) do tls_test do |port| # SERVER_ADDR is different from the hostname in the certificate, # so the following code should raise a SSLError. Net::FTP.new(SERVER_ADDR, :port => port, :ssl => { :ca_file => CA_FILE }) end end end def test_active_private_data_connection server = TCPServer.new(SERVER_ADDR, 0) port = server.addr[1] commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 @thread = Thread.start do sock = server.accept begin sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("234 AUTH success.\r\n") ctx = OpenSSL::SSL::SSLContext.new ctx.ca_file = CA_FILE ctx.key = File.open(SERVER_KEY) { |f| OpenSSL::PKey::RSA.new(f) } ctx.cert = File.open(SERVER_CERT) { |f| OpenSSL::X509::Certificate.new(f) } sock = OpenSSL::SSL::SSLSocket.new(sock, ctx) sock.sync_close = true begin sock.accept commands.push(sock.gets) sock.print("200 PSBZ success.\r\n") commands.push(sock.gets) sock.print("200 PROT success.\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") line = sock.gets commands.push(line) port_args = line.slice(/\APORT (.*)/, 1).split(/,/) host = port_args[0, 4].join(".") port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y} sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") conn = TCPSocket.new(host, port) conn = OpenSSL::SSL::SSLSocket.new(conn, ctx) conn.sync_close = true conn.accept binary_data.scan(/.{1,1024}/nm) do |s| conn.print(s) end conn.close sock.print("226 Transfer complete.\r\n") rescue OpenSSL::SSL::SSLError end ensure sock.close server.close end end ftp = Net::FTP.new("localhost", port: port, ssl: { ca_file: CA_FILE }, passive: false) begin assert_equal("AUTH TLS\r\n", commands.shift) assert_equal("PBSZ 0\r\n", commands.shift) assert_equal("PROT P\r\n", commands.shift) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) buf = ftp.getbinaryfile("foo", nil) assert_equal(binary_data, buf) assert_equal(Encoding::ASCII_8BIT, buf.encoding) assert_match(/\APORT /, commands.shift) assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close end end def test_passive_private_data_connection server = TCPServer.new(SERVER_ADDR, 0) port = server.addr[1] commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 @thread = Thread.start do sock = server.accept begin sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("234 AUTH success.\r\n") ctx = OpenSSL::SSL::SSLContext.new ctx.ca_file = CA_FILE ctx.key = File.open(SERVER_KEY) { |f| OpenSSL::PKey::RSA.new(f) } ctx.cert = File.open(SERVER_CERT) { |f| OpenSSL::X509::Certificate.new(f) } sock = OpenSSL::SSL::SSLSocket.new(sock, ctx) sock.sync_close = true begin sock.accept commands.push(sock.gets) sock.print("200 PSBZ success.\r\n") commands.push(sock.gets) sock.print("200 PROT success.\r\n") commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") commands.push(sock.gets) data_server = TCPServer.new(SERVER_ADDR, 0) port = data_server.local_address.ip_port sock.printf("227 Entering Passive Mode (127,0,0,1,%s).\r\n", port.divmod(256).join(",")) commands.push(sock.gets) sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") conn = data_server.accept conn = OpenSSL::SSL::SSLSocket.new(conn, ctx) conn.sync_close = true conn.accept binary_data.scan(/.{1,1024}/nm) do |s| conn.print(s) end conn.close data_server.close sock.print("226 Transfer complete.\r\n") rescue OpenSSL::SSL::SSLError end ensure sock.close server.close end end ftp = Net::FTP.new("localhost", port: port, ssl: { ca_file: CA_FILE }, passive: true) begin assert_equal("AUTH TLS\r\n", commands.shift) assert_equal("PBSZ 0\r\n", commands.shift) assert_equal("PROT P\r\n", commands.shift) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) buf = ftp.getbinaryfile("foo", nil) assert_equal(binary_data, buf) assert_equal(Encoding::ASCII_8BIT, buf.encoding) assert_equal("PASV\r\n", commands.shift) assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close end end def test_active_clear_data_connection server = TCPServer.new(SERVER_ADDR, 0) port = server.addr[1] commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 @thread = Thread.start do sock = server.accept begin sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("234 AUTH success.\r\n") ctx = OpenSSL::SSL::SSLContext.new ctx.ca_file = CA_FILE ctx.key = File.open(SERVER_KEY) { |f| OpenSSL::PKey::RSA.new(f) } ctx.cert = File.open(SERVER_CERT) { |f| OpenSSL::X509::Certificate.new(f) } sock = OpenSSL::SSL::SSLSocket.new(sock, ctx) sock.sync_close = true begin sock.accept commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") line = sock.gets commands.push(line) port_args = line.slice(/\APORT (.*)/, 1).split(/,/) host = port_args[0, 4].join(".") port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y} sock.print("200 PORT command successful.\r\n") commands.push(sock.gets) sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") conn = TCPSocket.new(host, port) binary_data.scan(/.{1,1024}/nm) do |s| conn.print(s) end conn.close sock.print("226 Transfer complete.\r\n") rescue OpenSSL::SSL::SSLError end ensure sock.close server.close end end ftp = Net::FTP.new("localhost", port: port, ssl: { ca_file: CA_FILE }, private_data_connection: false, passive: false) begin assert_equal("AUTH TLS\r\n", commands.shift) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) buf = ftp.getbinaryfile("foo", nil) assert_equal(binary_data, buf) assert_equal(Encoding::ASCII_8BIT, buf.encoding) assert_match(/\APORT /, commands.shift) assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close end end def test_passive_clear_data_connection server = TCPServer.new(SERVER_ADDR, 0) port = server.addr[1] commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 @thread = Thread.start do sock = server.accept begin sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("234 AUTH success.\r\n") ctx = OpenSSL::SSL::SSLContext.new ctx.ca_file = CA_FILE ctx.key = File.open(SERVER_KEY) { |f| OpenSSL::PKey::RSA.new(f) } ctx.cert = File.open(SERVER_CERT) { |f| OpenSSL::X509::Certificate.new(f) } sock = OpenSSL::SSL::SSLSocket.new(sock, ctx) sock.sync_close = true begin sock.accept commands.push(sock.gets) sock.print("331 Please specify the password.\r\n") commands.push(sock.gets) sock.print("230 Login successful.\r\n") commands.push(sock.gets) sock.print("200 Switching to Binary mode.\r\n") commands.push(sock.gets) data_server = TCPServer.new(SERVER_ADDR, 0) port = data_server.local_address.ip_port sock.printf("227 Entering Passive Mode (127,0,0,1,%s).\r\n", port.divmod(256).join(",")) commands.push(sock.gets) sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") conn = data_server.accept binary_data.scan(/.{1,1024}/nm) do |s| conn.print(s) end conn.close data_server.close sock.print("226 Transfer complete.\r\n") rescue OpenSSL::SSL::SSLError end ensure sock.close server.close end end ftp = Net::FTP.new("localhost", port: port, ssl: { ca_file: CA_FILE }, private_data_connection: false, passive: true) begin assert_equal("AUTH TLS\r\n", commands.shift) ftp.login assert_match(/\AUSER /, commands.shift) assert_match(/\APASS /, commands.shift) assert_equal("TYPE I\r\n", commands.shift) buf = ftp.getbinaryfile("foo", nil) assert_equal(binary_data, buf) assert_equal(Encoding::ASCII_8BIT, buf.encoding) assert_equal("PASV\r\n", commands.shift) assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) ensure ftp.close end end end private def create_ftp_server(sleep_time = nil) server = TCPServer.new(SERVER_ADDR, 0) @thread = Thread.start do if sleep_time sleep(sleep_time) end sock = server.accept begin sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 1) yield(sock) sock.shutdown(Socket::SHUT_WR) sock.read unless sock.eof? ensure sock.close end end def server.port addr[1] end return server end def tls_test server = TCPServer.new(SERVER_ADDR, 0) port = server.addr[1] commands = [] @thread = Thread.start do sock = server.accept begin sock.print("220 (test_ftp).\r\n") commands.push(sock.gets) sock.print("234 AUTH success.\r\n") ctx = OpenSSL::SSL::SSLContext.new ctx.ca_file = CA_FILE ctx.key = File.open(SERVER_KEY) { |f| OpenSSL::PKey::RSA.new(f) } ctx.cert = File.open(SERVER_CERT) { |f| OpenSSL::X509::Certificate.new(f) } sock = OpenSSL::SSL::SSLSocket.new(sock, ctx) sock.sync_close = true begin sock.accept commands.push(sock.gets) sock.print("200 PSBZ success.\r\n") commands.push(sock.gets) sock.print("200 PROT success.\r\n") rescue OpenSSL::SSL::SSLError end ensure sock.close server.close end end ftp = yield(port) ftp.close assert_equal("AUTH TLS\r\n", commands.shift) assert_equal("PBSZ 0\r\n", commands.shift) assert_equal("PROT P\r\n", commands.shift) end end