mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
2574 lines
81 KiB
Ruby
2574 lines
81 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "net/ftp"
|
|
require "test/unit"
|
|
require "ostruct"
|
|
require "stringio"
|
|
require "tempfile"
|
|
require "tmpdir"
|
|
|
|
class FTPTest < Test::Unit::TestCase
|
|
SERVER_NAME = "localhost"
|
|
SERVER_ADDR =
|
|
begin
|
|
Addrinfo.getaddrinfo(SERVER_NAME, 0, nil, :STREAM)[0].ip_address
|
|
rescue SocketError
|
|
"127.0.0.1"
|
|
end
|
|
CA_FILE = File.expand_path("../fixtures/cacert.pem", __dir__)
|
|
SERVER_KEY = File.expand_path("../fixtures/server.key", __dir__)
|
|
SERVER_CERT = File.expand_path("../fixtures/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(:@bare_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,
|
|
username: "foo",
|
|
password: "bar",
|
|
account: "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, username: "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
|
|
|
|
def test_s_new_timeout_options
|
|
ftp = Net::FTP.new
|
|
assert_equal(nil, ftp.open_timeout)
|
|
assert_equal(60, ftp.read_timeout)
|
|
|
|
ftp = Net::FTP.new(nil, open_timeout: 123, read_timeout: 234)
|
|
assert_equal(123, ftp.open_timeout)
|
|
assert_equal(234, ftp.read_timeout)
|
|
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(2.0) # Net::ReadTimeout
|
|
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.4
|
|
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 = 1.0
|
|
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(1.0, 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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
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(/\A(PORT|EPRT) /, 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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
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 = 1.0
|
|
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(/\A(PORT|EPRT) /, 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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
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")
|
|
[host, port]
|
|
}
|
|
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_raise(Net::FTPPermError){ ftp.list }
|
|
assert_equal("TYPE A\r\n", commands.shift)
|
|
assert_match(/\A(PORT|EPRT) /, 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.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(/\A(PORT|EPRT) /, 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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
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(1.0)
|
|
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.5
|
|
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(/\A(PORT|EPRT) /, 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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
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.2)
|
|
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 = 1.0
|
|
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(/\A(PORT|EPRT) /, 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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
commands.push(sock.gets)
|
|
sock.print("550 Requested action not taken.\r\n")
|
|
[host, port]
|
|
}
|
|
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_raise(Net::FTPPermError){ ftp.retrbinary("RETR foo", 1024) }
|
|
assert_match(/\A(PORT|EPRT) /, 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
|
|
# http://ci.rvm.jp/logfiles/brlog.trunk-mjit-wait.20200326-025942
|
|
skip 'This has been too unstable with --jit-wait' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
|
|
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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
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.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(/\A(PORT|EPRT) /, 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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
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.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(/\A(PORT|EPRT) /, 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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
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.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(/\A(PORT|EPRT) /, 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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
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.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(/\A(PORT|EPRT) /, 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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
commands.push(sock.gets)
|
|
sock.print("452 Requested file action aborted.\r\n")
|
|
[host, port]
|
|
}
|
|
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_raise(Net::FTPTempError){ ftp.storbinary("STOR foo", StringIO.new(binary_data), 1024) }
|
|
assert_match(/\A(PORT|EPRT) /, 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 = <<EOF.gsub(/\n/, "\r\n")
|
|
foo
|
|
bar
|
|
baz
|
|
EOF
|
|
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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
commands.push(sock.gets)
|
|
sock.print("150 Opening TEXT mode data connection for foo (#{text_data.size} bytes)\r\n")
|
|
conn = TCPSocket.new(host, port)
|
|
text_data.each_line do |l|
|
|
conn.print(l)
|
|
end
|
|
conn.shutdown(Socket::SHUT_WR)
|
|
conn.read
|
|
conn.close
|
|
sock.print("226 Transfer complete.\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)
|
|
buf = String.new
|
|
ftp.retrlines("RETR foo") do |line|
|
|
buf << line + "\r\n"
|
|
end
|
|
assert_equal(text_data.bytesize, buf.bytesize)
|
|
assert_equal(text_data, buf)
|
|
assert_equal("TYPE A\r\n", commands.shift)
|
|
assert_match(/\A(PORT|EPRT) /, commands.shift)
|
|
assert_equal("RETR foo\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_gettextfile
|
|
commands = []
|
|
text_data = <<EOF.gsub(/\n/, "\r\n")
|
|
foo
|
|
bar
|
|
baz
|
|
EOF
|
|
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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
commands.push(sock.gets)
|
|
sock.print("150 Opening TEXT mode data connection for foo (#{text_data.size} bytes)\r\n")
|
|
conn = TCPSocket.new(host, port)
|
|
text_data.each_line do |l|
|
|
conn.print(l)
|
|
end
|
|
conn.shutdown(Socket::SHUT_WR)
|
|
conn.read
|
|
conn.close
|
|
sock.print("226 Transfer complete.\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)
|
|
buf = ftp.gettextfile("foo", nil)
|
|
assert_equal(text_data.gsub(/\r\n/, "\n"), buf)
|
|
assert_equal(Encoding::ASCII_8BIT, buf.encoding)
|
|
assert_equal("TYPE A\r\n", commands.shift)
|
|
assert_match(/\A(PORT|EPRT) /, commands.shift)
|
|
assert_equal("RETR foo\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_gettextfile_with_filename_and_block
|
|
commands = []
|
|
text_data = <<EOF.gsub(/\n/, "\r\n")
|
|
foo
|
|
bar
|
|
baz
|
|
EOF
|
|
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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
commands.push(sock.gets)
|
|
sock.print("150 Opening TEXT mode data connection for foo (#{text_data.size} bytes)\r\n")
|
|
conn = TCPSocket.new(host, port)
|
|
text_data.each_line do |l|
|
|
conn.print(l)
|
|
end
|
|
conn.shutdown(Socket::SHUT_WR)
|
|
conn.read
|
|
conn.close
|
|
sock.print("226 Transfer complete.\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)
|
|
Tempfile.create("foo", external_encoding: "ascii-8bit") do |f|
|
|
buf = String.new
|
|
res = ftp.gettextfile("foo", f.path) { |s|
|
|
buf << s << "\n"
|
|
}
|
|
assert_equal(nil, res)
|
|
assert_equal(text_data.gsub(/\r\n/, "\n"), buf)
|
|
assert_equal(Encoding::ASCII_8BIT, buf.encoding)
|
|
assert_equal(buf, f.read)
|
|
end
|
|
assert_equal("TYPE A\r\n", commands.shift)
|
|
assert_match(/\A(PORT|EPRT) /, commands.shift)
|
|
assert_equal("RETR foo\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_getbinaryfile_in_list
|
|
commands = []
|
|
binary_data = (0..0xff).map {|i| i.chr}.join
|
|
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.bin"
|
|
]
|
|
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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
commands.push(sock.gets)
|
|
sock.print("150 Here comes the directory listing.\r\n")
|
|
conn = TCPSocket.new(host, port)
|
|
list_lines.each_with_index do |l, i|
|
|
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")
|
|
line = sock.gets
|
|
commands.push(line)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
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.print(binary_data)
|
|
conn.close
|
|
sock.print("226 Transfer complete.\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)
|
|
ftp.list do |line|
|
|
file = line.slice(/(\S*\.bin)\z/)
|
|
if file
|
|
data = ftp.getbinaryfile(file, nil)
|
|
assert_equal(binary_data, data)
|
|
end
|
|
end
|
|
assert_equal("TYPE A\r\n", commands.shift)
|
|
assert_match(/\A(PORT|EPRT) /, commands.shift)
|
|
assert_equal("LIST\r\n", commands.shift)
|
|
assert_equal("TYPE I\r\n", commands.shift)
|
|
assert_match(/\A(PORT|EPRT) /, commands.shift)
|
|
assert_equal("RETR baz.bin\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_abort
|
|
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("225 No transfer to ABOR.\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)
|
|
ftp.abort
|
|
assert_equal("ABOR\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_status
|
|
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("211 End of status\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)
|
|
ftp.status
|
|
assert_equal("STAT\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_status_path
|
|
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("213 End of status\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)
|
|
ftp.status "/"
|
|
assert_equal("STAT /\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_pathnames
|
|
require 'pathname'
|
|
|
|
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")
|
|
commands.push(sock.gets)
|
|
sock.print("257 'foo' directory created.\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("250 CWD command successful.\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("250 CWD command successful.\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("250 RMD command successful.\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("213 test.txt Fri, 11 Jan 2013 11:20:41 -0500.\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("213 test.txt 16.\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("350 File exists, ready for destination name\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("250 RNTO command successful.\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("250 DELE command successful.\r\n")
|
|
}
|
|
|
|
begin
|
|
begin
|
|
dir = Pathname.new("foo")
|
|
file = Pathname.new("test.txt")
|
|
file2 = Pathname.new("test2.txt")
|
|
ftp = Net::FTP.new
|
|
ftp.connect(SERVER_ADDR, server.port)
|
|
ftp.login
|
|
ftp.mkdir(dir)
|
|
ftp.chdir(dir)
|
|
ftp.chdir("..")
|
|
ftp.rmdir(dir)
|
|
ftp.mdtm(file)
|
|
ftp.size(file)
|
|
ftp.rename(file, file2)
|
|
ftp.delete(file)
|
|
|
|
# TODO: These commented tests below expose the error but don't test anything:
|
|
# TypeError: no implicit conversion of Pathname into String
|
|
# ftp.nlst(dir)
|
|
# ftp.putbinaryfile(Pathname.new("/etc/hosts"), file2)
|
|
# ftp.puttextfile(Pathname.new("/etc/hosts"), file2)
|
|
# ftp.gettextfile(Pathname.new("/etc/hosts"), file2)
|
|
# ftp.getbinaryfile(Pathname.new("/etc/hosts"), file2)
|
|
# ftp.list(dir, dir, dir)
|
|
|
|
assert_match(/\AUSER /, commands.shift)
|
|
assert_match(/\APASS /, commands.shift)
|
|
assert_match(/\ATYPE /, commands.shift)
|
|
assert_match(/\AMKD /, commands.shift)
|
|
assert_match(/\ACWD /, commands.shift)
|
|
assert_match(/\ACDUP/, commands.shift)
|
|
assert_match(/\ARMD /, commands.shift)
|
|
assert_match(/\AMDTM /, commands.shift)
|
|
assert_match(/\ASIZE /, commands.shift)
|
|
assert_match(/\ARNFR /, commands.shift)
|
|
assert_match(/\ARNTO /, commands.shift)
|
|
assert_match(/\ADELE /, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_getmultiline
|
|
server = create_ftp_server { |sock|
|
|
sock.print("220 (test_ftp).\r\n")
|
|
sock.print("123- foo\r\n")
|
|
sock.print("bar\r\n")
|
|
sock.print(" 123 baz\r\n")
|
|
sock.print("123 quux\r\n")
|
|
sock.print("123 foo\r\n")
|
|
sock.print("foo\r\n")
|
|
sock.print("\r\n")
|
|
}
|
|
begin
|
|
begin
|
|
ftp = Net::FTP.new
|
|
ftp.connect(SERVER_ADDR, server.port)
|
|
assert_equal("123- foo\nbar\n 123 baz\n123 quux\n",
|
|
ftp.send(:getmultiline))
|
|
assert_equal("123 foo\n", ftp.send(:getmultiline))
|
|
assert_equal("foo\n", ftp.send(:getmultiline))
|
|
assert_equal("\n", ftp.send(:getmultiline))
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_size
|
|
commands = []
|
|
server = create_ftp_server { |sock|
|
|
sock.print("220 (test_ftp).\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("213 12345\r\n")
|
|
}
|
|
begin
|
|
begin
|
|
ftp = Net::FTP.new
|
|
ftp.connect(SERVER_ADDR, server.port)
|
|
assert_equal(12345, ftp.size("foo.txt"))
|
|
assert_match("SIZE foo.txt\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_mdtm
|
|
commands = []
|
|
server = create_ftp_server { |sock|
|
|
sock.print("220 (test_ftp).\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("213 20150910161739\r\n")
|
|
}
|
|
begin
|
|
begin
|
|
ftp = Net::FTP.new
|
|
ftp.connect(SERVER_ADDR, server.port)
|
|
assert_equal("20150910161739", ftp.mdtm("foo.txt"))
|
|
assert_match("MDTM foo.txt\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_mtime
|
|
commands = []
|
|
server = create_ftp_server { |sock|
|
|
sock.print("220 (test_ftp).\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("213 20150910161739\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("213 20150910161739\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("213 20150910161739.123456\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("213 20150910161739.123\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("213 20150910161739.123456789\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("213 2015091016173\r\n")
|
|
}
|
|
begin
|
|
begin
|
|
ftp = Net::FTP.new
|
|
ftp.connect(SERVER_ADDR, server.port)
|
|
assert_equal(Time.utc(2015, 9, 10, 16, 17, 39), ftp.mtime("foo.txt"))
|
|
assert_equal(Time.local(2015, 9, 10, 16, 17, 39),
|
|
ftp.mtime("foo.txt", true))
|
|
assert_equal(Time.utc(2015, 9, 10, 16, 17, 39, 123456),
|
|
ftp.mtime("bar.txt"))
|
|
assert_equal(Time.utc(2015, 9, 10, 16, 17, 39, 123000),
|
|
ftp.mtime("bar.txt"))
|
|
assert_equal(Time.utc(2015, 9, 10, 16, 17, 39,
|
|
Rational(123456789, 1000)),
|
|
ftp.mtime("bar.txt"))
|
|
assert_raise(Net::FTPProtoError) do
|
|
ftp.mtime("quux.txt")
|
|
end
|
|
assert_match("MDTM foo.txt\r\n", commands.shift)
|
|
assert_match("MDTM foo.txt\r\n", commands.shift)
|
|
assert_match("MDTM bar.txt\r\n", commands.shift)
|
|
assert_match("MDTM bar.txt\r\n", commands.shift)
|
|
assert_match("MDTM bar.txt\r\n", commands.shift)
|
|
assert_match("MDTM quux.txt\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_system
|
|
commands = []
|
|
server = create_ftp_server { |sock|
|
|
sock.print("220 (test_ftp).\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("215 UNIX Type: L8\r\n")
|
|
}
|
|
begin
|
|
begin
|
|
ftp = Net::FTP.new
|
|
ftp.connect(SERVER_ADDR, server.port)
|
|
assert_equal("UNIX Type: L8", ftp.system)
|
|
assert_match("SYST\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_features
|
|
commands = []
|
|
server = create_ftp_server { |sock|
|
|
sock.print("220 (test_ftp).\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("211-Features\r\n")
|
|
sock.print(" LANG EN*\r\n")
|
|
sock.print(" UTF8\r\n")
|
|
sock.print("211 End\r\n")
|
|
}
|
|
begin
|
|
begin
|
|
ftp = Net::FTP.new
|
|
ftp.connect(SERVER_ADDR, server.port)
|
|
assert_equal(['LANG EN*', 'UTF8'], ftp.features)
|
|
assert_equal("FEAT\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_features_not_implemented
|
|
commands = []
|
|
server = create_ftp_server { |sock|
|
|
sock.print("220 (test_ftp).\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("502 Not Implemented\r\n")
|
|
}
|
|
begin
|
|
begin
|
|
ftp = Net::FTP.new
|
|
ftp.connect(SERVER_ADDR, server.port)
|
|
assert_raise(Net::FTPPermError) do
|
|
ftp.features
|
|
end
|
|
assert_equal("FEAT\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
|
|
end
|
|
|
|
def test_option
|
|
commands = []
|
|
server = create_ftp_server { |sock|
|
|
sock.print("220 (test_ftp)\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("200 OPTS UTF8 command successful\r\n")
|
|
}
|
|
begin
|
|
begin
|
|
ftp = Net::FTP.new
|
|
ftp.connect(SERVER_ADDR, server.port)
|
|
ftp.option("UTF8", "ON")
|
|
assert_equal("OPTS UTF8 ON\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_option_not_implemented
|
|
commands = []
|
|
server = create_ftp_server { |sock|
|
|
sock.print("220 (test_ftp)\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("502 Not implemented\r\n")
|
|
}
|
|
begin
|
|
begin
|
|
ftp = Net::FTP.new
|
|
ftp.connect(SERVER_ADDR, server.port)
|
|
assert_raise(Net::FTPPermError) do
|
|
ftp.option("UTF8", "ON")
|
|
end
|
|
assert_equal("OPTS UTF8 ON\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_mlst
|
|
commands = []
|
|
server = create_ftp_server { |sock|
|
|
sock.print("220 (test_ftp).\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("250- Listing foo\r\n")
|
|
sock.print(" Type=file;Unique=FC00U1E554A;Size=1234567;Modify=20131220035929;Perm=r;Unix.mode=0644;Unix.owner=122;Unix.group=0;Unix.ctime=20131220120140;Unix.atime=20131220131139; /foo\r\n")
|
|
sock.print("250 End\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("250 Malformed response\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("250- Listing foo\r\n")
|
|
sock.print("\r\n")
|
|
sock.print("250 End\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("250- Listing foo\r\n")
|
|
sock.print(" abc /foo\r\n")
|
|
sock.print("250 End\r\n")
|
|
}
|
|
begin
|
|
begin
|
|
ftp = Net::FTP.new
|
|
ftp.connect(SERVER_ADDR, server.port)
|
|
entry = ftp.mlst("foo")
|
|
assert_equal("/foo", entry.pathname)
|
|
assert_equal("file", entry.facts["type"])
|
|
assert_equal("FC00U1E554A", entry.facts["unique"])
|
|
assert_equal(1234567, entry.facts["size"])
|
|
assert_equal("r", entry.facts["perm"])
|
|
assert_equal(0644, entry.facts["unix.mode"])
|
|
assert_equal(122, entry.facts["unix.owner"])
|
|
assert_equal(0, entry.facts["unix.group"])
|
|
modify = entry.facts["modify"]
|
|
assert_equal(2013, modify.year)
|
|
assert_equal(12, modify.month)
|
|
assert_equal(20, modify.day)
|
|
assert_equal(3, modify.hour)
|
|
assert_equal(59, modify.min)
|
|
assert_equal(29, modify.sec)
|
|
assert_equal(true, modify.utc?)
|
|
ctime = entry.facts["unix.ctime"]
|
|
assert_equal(12, ctime.hour)
|
|
assert_equal(1, ctime.min)
|
|
assert_equal(40, ctime.sec)
|
|
atime = entry.facts["unix.atime"]
|
|
assert_equal(13, atime.hour)
|
|
assert_equal(11, atime.min)
|
|
assert_equal(39, atime.sec)
|
|
assert_match("MLST foo\r\n", commands.shift)
|
|
assert_raise(Net::FTPProtoError) do
|
|
ftp.mlst("foo")
|
|
end
|
|
assert_match("MLST foo\r\n", commands.shift)
|
|
assert_raise(Net::FTPProtoError) do
|
|
ftp.mlst("foo")
|
|
end
|
|
assert_match("MLST foo\r\n", commands.shift)
|
|
entry = ftp.mlst("foo")
|
|
assert_equal("/foo", entry.pathname)
|
|
assert_match("MLST foo\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_mlsd
|
|
commands = []
|
|
entry_lines = [
|
|
"Type=file;Unique=FC00U1E554A;Size=1234567;Modify=20131220035929.123456;Perm=r; foo bar",
|
|
"Type=cdir;Unique=FC00U1E554B;Modify=20131220035929;Perm=flcdmpe; .",
|
|
"Type=pdir;Unique=FC00U1E554C;Modify=20131220035929;Perm=flcdmpe; ..",
|
|
]
|
|
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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
commands.push(sock.gets)
|
|
sock.print("150 Here comes the directory listing.\r\n")
|
|
begin
|
|
conn = TCPSocket.new(host, port)
|
|
entry_lines.each do |l|
|
|
conn.print(l, "\r\n")
|
|
end
|
|
rescue Errno::EPIPE
|
|
ensure
|
|
assert_nil($!)
|
|
conn.close
|
|
end
|
|
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.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)
|
|
entries = ftp.mlsd("/")
|
|
assert_equal(3, entries.size)
|
|
assert_equal("foo bar", entries[0].pathname)
|
|
assert_equal(".", entries[1].pathname)
|
|
assert_equal("..", entries[2].pathname)
|
|
assert_equal("file", entries[0].facts["type"])
|
|
assert_equal("cdir", entries[1].facts["type"])
|
|
assert_equal("pdir", entries[2].facts["type"])
|
|
assert_equal("flcdmpe", entries[1].facts["perm"])
|
|
modify = entries[0].facts["modify"]
|
|
assert_equal(2013, modify.year)
|
|
assert_equal(12, modify.month)
|
|
assert_equal(20, modify.day)
|
|
assert_equal(3, modify.hour)
|
|
assert_equal(59, modify.min)
|
|
assert_equal(29, modify.sec)
|
|
assert_equal(123456, modify.usec)
|
|
assert_equal(true, modify.utc?)
|
|
assert_equal("TYPE A\r\n", commands.shift)
|
|
assert_match(/\A(PORT|EPRT) /, commands.shift)
|
|
assert_match("MLSD /\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_parse257
|
|
ftp = Net::FTP.new
|
|
assert_equal('/foo/bar',
|
|
ftp.send(:parse257, '257 "/foo/bar" directory created'))
|
|
assert_equal('/foo/bar"baz',
|
|
ftp.send(:parse257, '257 "/foo/bar""baz" directory created'))
|
|
assert_equal('/foo/x"y"z',
|
|
ftp.send(:parse257, '257 "/foo/x""y""z" directory created'))
|
|
assert_equal('/foo/bar',
|
|
ftp.send(:parse257, '257 "/foo/bar" "comment"'))
|
|
assert_equal('',
|
|
ftp.send(:parse257, '257 "" directory created'))
|
|
assert_equal('',
|
|
ftp.send(:parse257, '257 directory created'))
|
|
assert_raise(Net::FTPReplyError) do
|
|
ftp.send(:parse257, "500 Syntax error")
|
|
end
|
|
end
|
|
|
|
def test_putline_reject_crlf
|
|
ftp = Net::FTP.new
|
|
assert_raise(ArgumentError) do
|
|
ftp.send(:putline, "\r")
|
|
end
|
|
assert_raise(ArgumentError) do
|
|
ftp.send(:putline, "\n")
|
|
end
|
|
end
|
|
|
|
if defined?(OpenSSL::SSL)
|
|
def test_tls_unknown_ca
|
|
assert_raise(OpenSSL::SSL::SSLError) do
|
|
tls_test do |port|
|
|
begin
|
|
Net::FTP.new(SERVER_NAME,
|
|
:port => 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(SERVER_NAME,
|
|
: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 = []
|
|
session_reused_for_data_connection = nil
|
|
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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
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
|
|
session_reused_for_data_connection = conn.session_reused?
|
|
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(SERVER_NAME,
|
|
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(/\A(PORT|EPRT) /, commands.shift)
|
|
assert_equal("RETR foo\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
# FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h.
|
|
# See https://github.com/openssl/openssl/pull/5967 for details.
|
|
if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h/
|
|
assert_equal(true, session_reused_for_data_connection)
|
|
end
|
|
ensure
|
|
ftp.close
|
|
end
|
|
end
|
|
|
|
def test_passive_private_data_connection
|
|
server = TCPServer.new(SERVER_ADDR, 0)
|
|
port = server.addr[1]
|
|
commands = []
|
|
session_reused_for_data_connection = nil
|
|
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 = create_data_connection_server(sock)
|
|
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
|
|
session_reused_for_data_connection = conn.session_reused?
|
|
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(SERVER_NAME,
|
|
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_match(/\A(PASV|EPSV)\r\n/, commands.shift)
|
|
assert_equal("RETR foo\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
# FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h.
|
|
if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h/
|
|
assert_equal(true, session_reused_for_data_connection)
|
|
end
|
|
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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
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(SERVER_NAME,
|
|
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(/\A(PORT|EPRT) /, 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 = create_data_connection_server(sock)
|
|
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(SERVER_NAME,
|
|
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_match(/\A(PASV|EPSV)\r\n/, commands.shift)
|
|
assert_equal("RETR foo\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close
|
|
end
|
|
end
|
|
|
|
def test_tls_connect_timeout
|
|
server = TCPServer.new(SERVER_ADDR, 0)
|
|
port = server.addr[1]
|
|
commands = []
|
|
sock = nil
|
|
@thread = Thread.start do
|
|
sock = server.accept
|
|
sock.print("220 (test_ftp).\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("234 AUTH success.\r\n")
|
|
end
|
|
begin
|
|
assert_raise(Net::OpenTimeout) do
|
|
Net::FTP.new(SERVER_NAME,
|
|
port: port,
|
|
ssl: { ca_file: CA_FILE },
|
|
ssl_handshake_timeout: 0.1)
|
|
end
|
|
@thread.join
|
|
ensure
|
|
sock.close if sock
|
|
server.close
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_abort_tls
|
|
return unless defined?(OpenSSL)
|
|
# http://ci.rvm.jp/results/trunk-mjit-wait@silicon-docker/2789353
|
|
skip 'This is unstable with --jit-wait. TODO: debug it' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
|
|
|
|
commands = []
|
|
server = create_ftp_server { |sock|
|
|
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
|
|
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)
|
|
sock.print("225 No transfer to ABOR.\r\n")
|
|
}
|
|
begin
|
|
begin
|
|
ftp = Net::FTP.new(SERVER_NAME,
|
|
port: server.port,
|
|
ssl: { ca_file: CA_FILE })
|
|
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)
|
|
ftp.abort
|
|
assert_equal("ABOR\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
rescue RuntimeError, LoadError
|
|
# skip (require openssl)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_getbinaryfile_command_injection
|
|
skip "| is not allowed in filename on Windows" if windows?
|
|
[false, true].each do |resume|
|
|
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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
commands.push(sock.gets)
|
|
sock.print("150 Opening BINARY mode data connection for |echo hello (#{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
|
|
chdir_to_tmpdir do
|
|
begin
|
|
ftp = Net::FTP.new
|
|
ftp.resume = resume
|
|
ftp.read_timeout = (defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?) ? 5 : 0.2 # use large timeout for --jit-wait
|
|
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.getbinaryfile("|echo hello")
|
|
assert_equal(binary_data, File.binread("./|echo hello"))
|
|
assert_match(/\A(PORT|EPRT) /, commands.shift)
|
|
assert_equal("RETR |echo hello\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_gettextfile_command_injection
|
|
skip "| is not allowed in filename on Windows" if windows?
|
|
commands = []
|
|
text_data = <<EOF.gsub(/\n/, "\r\n")
|
|
foo
|
|
bar
|
|
baz
|
|
EOF
|
|
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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
commands.push(sock.gets)
|
|
sock.print("150 Opening TEXT mode data connection for |echo hello (#{text_data.size} bytes)\r\n")
|
|
conn = TCPSocket.new(host, port)
|
|
text_data.each_line do |l|
|
|
conn.print(l)
|
|
end
|
|
conn.shutdown(Socket::SHUT_WR)
|
|
conn.read
|
|
conn.close
|
|
sock.print("226 Transfer complete.\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("200 Switching to Binary mode.\r\n")
|
|
}
|
|
begin
|
|
chdir_to_tmpdir do
|
|
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)
|
|
ftp.gettextfile("|echo hello")
|
|
assert_equal(text_data.gsub(/\r\n/, "\n"),
|
|
File.binread("./|echo hello"))
|
|
assert_equal("TYPE A\r\n", commands.shift)
|
|
assert_match(/\A(PORT|EPRT) /, commands.shift)
|
|
assert_equal("RETR |echo hello\r\n", commands.shift)
|
|
assert_equal("TYPE I\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_putbinaryfile_command_injection
|
|
skip "| is not allowed in filename on Windows" if windows?
|
|
commands = []
|
|
binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3
|
|
received_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)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
commands.push(sock.gets)
|
|
sock.print("150 Opening BINARY mode data connection for |echo hello (#{binary_data.size} bytes)\r\n")
|
|
conn = TCPSocket.new(host, port)
|
|
received_data = conn.read
|
|
conn.close
|
|
sock.print("226 Transfer complete.\r\n")
|
|
}
|
|
begin
|
|
chdir_to_tmpdir do
|
|
File.binwrite("./|echo hello", binary_data)
|
|
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.putbinaryfile("|echo hello")
|
|
assert_equal(binary_data, received_data)
|
|
assert_match(/\A(PORT|EPRT) /, commands.shift)
|
|
assert_equal("STOR |echo hello\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
end
|
|
ensure
|
|
server.close
|
|
end
|
|
end
|
|
|
|
def test_puttextfile_command_injection
|
|
skip "| is not allowed in filename on Windows" if windows?
|
|
commands = []
|
|
received_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")
|
|
commands.push(sock.gets)
|
|
sock.print("200 Switching to ASCII mode.\r\n")
|
|
line = sock.gets
|
|
commands.push(line)
|
|
host, port = process_port_or_eprt(sock, line)
|
|
commands.push(sock.gets)
|
|
sock.print("150 Opening TEXT mode data connection for |echo hello\r\n")
|
|
conn = TCPSocket.new(host, port)
|
|
received_data = conn.read
|
|
conn.close
|
|
sock.print("226 Transfer complete.\r\n")
|
|
commands.push(sock.gets)
|
|
sock.print("200 Switching to Binary mode.\r\n")
|
|
}
|
|
begin
|
|
chdir_to_tmpdir do
|
|
File.open("|echo hello", "w") do |f|
|
|
f.puts("foo")
|
|
f.puts("bar")
|
|
f.puts("baz")
|
|
end
|
|
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)
|
|
ftp.puttextfile("|echo hello")
|
|
assert_equal(<<EOF.gsub(/\n/, "\r\n"), received_data)
|
|
foo
|
|
bar
|
|
baz
|
|
EOF
|
|
assert_equal("TYPE A\r\n", commands.shift)
|
|
assert_match(/\A(PORT|EPRT) /, commands.shift)
|
|
assert_equal("STOR |echo hello\r\n", commands.shift)
|
|
assert_equal("TYPE I\r\n", commands.shift)
|
|
assert_equal(nil, commands.shift)
|
|
ensure
|
|
ftp.close if ftp
|
|
end
|
|
end
|
|
ensure
|
|
server.close
|
|
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, SystemCallError
|
|
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
|
|
|
|
def process_port_or_eprt(sock, line)
|
|
case line
|
|
when /\APORT (.*)/
|
|
port_args = $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")
|
|
return host, port
|
|
when /\AEPRT \|2\|(.*?)\|(.*?)\|/
|
|
host = $1
|
|
port = $2.to_i
|
|
sock.print("200 EPRT command successful.\r\n")
|
|
return host, port
|
|
else
|
|
flunk "PORT or EPRT expected"
|
|
end
|
|
end
|
|
|
|
def create_data_connection_server(sock)
|
|
data_server = TCPServer.new(SERVER_ADDR, 0)
|
|
port = data_server.local_address.ip_port
|
|
if data_server.local_address.ipv4?
|
|
sock.printf("227 Entering Passive Mode (127,0,0,1,%s).\r\n",
|
|
port.divmod(256).join(","))
|
|
elsif data_server.local_address.ipv6?
|
|
sock.printf("229 Entering Extended Passive Mode (|||%d|)\r\n", port)
|
|
else
|
|
flunk "Invalid local address"
|
|
end
|
|
return data_server
|
|
end
|
|
|
|
def chdir_to_tmpdir
|
|
Dir.mktmpdir do |dir|
|
|
pwd = Dir.pwd
|
|
Dir.chdir(dir)
|
|
begin
|
|
yield
|
|
ensure
|
|
Dir.chdir(pwd)
|
|
end
|
|
end
|
|
end
|
|
end
|