1
0
Fork 0
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:
shugo 2012-03-31 04:44:36 +00:00
parent a3626110d9
commit 5f81a53fd2
3 changed files with 505 additions and 8 deletions

View file

@ -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.

View file

@ -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

View file

@ -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