mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
* lib/net/ftp.rb (read_timeout=, open_timeout=): supported timeout.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@35194 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
a3626110d9
commit
5f81a53fd2
3 changed files with 505 additions and 8 deletions
|
@ -1,3 +1,7 @@
|
|||
Sat Mar 31 13:42:39 2012 Shugo Maeda <shugo@ruby-lang.org>
|
||||
|
||||
* lib/net/ftp.rb (read_timeout=, open_timeout=): supported timeout.
|
||||
|
||||
Sat Mar 31 13:20:40 2012 Sokolov Yura (funny-falcon) <funny.falcon@gmail.com>
|
||||
|
||||
* hash.c: remove unnecessary checks for Qundef in hash iterations.
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
require "socket"
|
||||
require "monitor"
|
||||
require "net/protocol"
|
||||
|
||||
module Net
|
||||
|
||||
|
@ -76,7 +77,7 @@ module Net
|
|||
# :stopdoc:
|
||||
FTP_PORT = 21
|
||||
CRLF = "\r\n"
|
||||
DEFAULT_BLOCKSIZE = 4096
|
||||
DEFAULT_BLOCKSIZE = BufferedIO::BUFSIZE
|
||||
# :startdoc:
|
||||
|
||||
# When +true+, transfers are performed in binary mode. Default: +true+.
|
||||
|
@ -93,6 +94,24 @@ module Net
|
|||
# transfers are resumed or restarted. Default: +false+.
|
||||
attr_accessor :resume
|
||||
|
||||
# Number of seconds to wait for the connection to open. Any number
|
||||
# may be used, including Floats for fractional seconds. If the FTP
|
||||
# object cannot open a connection in this many seconds, it raises a
|
||||
# Net::OpenTimeout exception.
|
||||
attr_accessor :open_timeout
|
||||
|
||||
# Number of seconds to wait for one block to be read (via one read(2)
|
||||
# call). Any number may be used, including Floats for fractional
|
||||
# seconds. If the FTP object cannot read data in this many seconds,
|
||||
# it raises a TimeoutError exception.
|
||||
attr_reader :read_timeout
|
||||
|
||||
# Setter for the read_timeout attribute.
|
||||
def read_timeout=(sec)
|
||||
@sock.read_timeout = sec
|
||||
@read_timeout = sec
|
||||
end
|
||||
|
||||
# The server's welcome message.
|
||||
attr_reader :welcome
|
||||
|
||||
|
@ -135,6 +154,8 @@ module Net
|
|||
@resume = false
|
||||
@sock = NullSocket.new
|
||||
@logged_in = false
|
||||
@open_timeout = nil
|
||||
@read_timeout = 60
|
||||
if host
|
||||
connect(host)
|
||||
if user
|
||||
|
@ -199,12 +220,17 @@ module Net
|
|||
# SOCKS_SERVER, then a SOCKSSocket is returned, else a TCPSocket is
|
||||
# returned.
|
||||
def open_socket(host, port) # :nodoc:
|
||||
if defined? SOCKSSocket and ENV["SOCKS_SERVER"]
|
||||
@passive = true
|
||||
return SOCKSSocket.open(host, port)
|
||||
else
|
||||
return TCPSocket.open(host, port)
|
||||
end
|
||||
return Timeout.timeout(@open_timeout, Net::OpenTimeout) {
|
||||
if defined? SOCKSSocket and ENV["SOCKS_SERVER"]
|
||||
@passive = true
|
||||
sock = SOCKSSocket.open(host, port)
|
||||
else
|
||||
sock = TCPSocket.open(host, port)
|
||||
end
|
||||
io = BufferedSocket.new(sock)
|
||||
io.read_timeout = @read_timeout
|
||||
io
|
||||
}
|
||||
end
|
||||
private :open_socket
|
||||
|
||||
|
@ -405,7 +431,8 @@ module Net
|
|||
if resp[0] != ?1
|
||||
raise FTPReplyError, resp
|
||||
end
|
||||
conn = sock.accept
|
||||
conn = BufferedSocket.new(sock.accept)
|
||||
conn.read_timeout = @read_timeout
|
||||
sock.close
|
||||
end
|
||||
return conn
|
||||
|
@ -1016,10 +1043,47 @@ module Net
|
|||
|
||||
# :stopdoc:
|
||||
class NullSocket
|
||||
def read_timeout=(sec)
|
||||
end
|
||||
|
||||
def close
|
||||
end
|
||||
|
||||
def method_missing(mid, *args)
|
||||
raise FTPConnectionError, "not connected"
|
||||
end
|
||||
end
|
||||
|
||||
class BufferedSocket < BufferedIO
|
||||
[:addr, :peeraddr].each do |method|
|
||||
define_method(method) { |*args|
|
||||
@io.__send__(method, *args)
|
||||
}
|
||||
end
|
||||
|
||||
def read(len = nil)
|
||||
if len
|
||||
s = super(len, "", true)
|
||||
return s.empty? ? nil : s
|
||||
else
|
||||
result = ""
|
||||
while s = super(BUFSIZ, "", true)
|
||||
result << s
|
||||
end
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
def gets
|
||||
return readuntil("\n")
|
||||
rescue EOFError
|
||||
return nil
|
||||
end
|
||||
|
||||
def readline
|
||||
return readuntil("\n")
|
||||
end
|
||||
end
|
||||
# :startdoc:
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
require "net/ftp"
|
||||
require "test/unit"
|
||||
require "ostruct"
|
||||
require "stringio"
|
||||
|
||||
class FTPTest < Test::Unit::TestCase
|
||||
SERVER_ADDR = "127.0.0.1"
|
||||
|
||||
def test_not_connected
|
||||
ftp = Net::FTP.new
|
||||
assert_raise(Net::FTPConnectionError) do
|
||||
|
@ -107,4 +110,430 @@ class FTPTest < Test::Unit::TestCase
|
|||
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
|
||||
|
||||
# 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(Timeout::Error) 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 if ftp
|
||||
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")
|
||||
conn = TCPSocket.new(host, port)
|
||||
list_lines.each_with_index do |l, i|
|
||||
if i == 1
|
||||
sleep(0.3)
|
||||
else
|
||||
sleep(0.1)
|
||||
end
|
||||
conn.print(l, "\r\n")
|
||||
end
|
||||
conn.close
|
||||
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(Timeout::Error) 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_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)
|
||||
binary_data.scan(/.{1,1024}/nm).each_with_index do |s, i|
|
||||
if i == 1
|
||||
sleep(0.3)
|
||||
else
|
||||
sleep(0.1)
|
||||
end
|
||||
conn.print(s)
|
||||
end
|
||||
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 = ""
|
||||
assert_raise(Timeout::Error) do
|
||||
ftp.retrbinary("RETR foo", 1024) do |s|
|
||||
buf << s
|
||||
end
|
||||
end
|
||||
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 if ftp
|
||||
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.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.retrbinary("RETR foo", 1024) do |s|
|
||||
buf << s
|
||||
end
|
||||
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_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
|
||||
|
||||
private
|
||||
|
||||
|
||||
def create_ftp_server(sleep_time = nil)
|
||||
server = TCPServer.new(SERVER_ADDR, 0)
|
||||
Thread.start do
|
||||
begin
|
||||
if sleep_time
|
||||
sleep(sleep_time)
|
||||
end
|
||||
sock = server.accept
|
||||
begin
|
||||
yield(sock)
|
||||
ensure
|
||||
sock.close
|
||||
end
|
||||
rescue
|
||||
end
|
||||
end
|
||||
def server.port
|
||||
addr[1]
|
||||
end
|
||||
return server
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue