ruby-changes:46435
From: shugo <ko1@a...>
Date: Wed, 3 May 2017 20:32:27 +0900 (JST)
Subject: [ruby-changes:46435] shugo:r58549 (trunk): net/imap: handle timeouts
shugo 2017-05-03 20:32:22 +0900 (Wed, 03 May 2017) New Revision: 58549 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=58549 Log: net/imap: handle timeouts Patch by Pavel Rosick?\195?\189. [Feature #13379] [ruby-core:80440] Modified files: trunk/lib/net/imap.rb Index: lib/net/imap.rb =================================================================== --- lib/net/imap.rb (revision 58548) +++ lib/net/imap.rb (revision 58549) @@ -18,6 +18,7 @@ require "socket" https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L18 require "monitor" require "digest/md5" require "strscan" +require 'net/protocol' begin require "openssl" rescue LoadError @@ -199,7 +200,7 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L200 # Goldsmith, D. and Davis, M., "UTF-7: A Mail-Safe Transformation Format of # Unicode", RFC 2152, May 1997. # - class IMAP + class IMAP < Protocol include MonitorMixin if defined?(OpenSSL::SSL) include OpenSSL @@ -221,6 +222,16 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L222 # Returns all response handlers. attr_reader :response_handlers + # Seconds to wait until a connection is opened. + # If the IMAP object cannot open a connection within this time, + # it raises a Net::OpenTimeout exception. The default value is 30 seconds. + attr_reader :open_timeout + + # Seconds to wait until reading one block (by one read(1) call). + # If the IMAP object cannot complete a read() within this time, + # it raises a Net::ReadTimeout exception. The default value is 60 seconds. + attr_reader :read_timeout + # The thread to receive exceptions. attr_accessor :client_thread @@ -1048,6 +1059,8 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L1059 # be installed. # If options[:ssl] is a hash, it's passed to # OpenSSL::SSL::SSLContext#set_params as parameters. + # open_timeout:: Seconds to wait until a connection is opened + # read_timeout:: Seconds to wait until reading one block # # The most common errors are: # @@ -1076,8 +1089,10 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L1089 @port = options[:port] || (options[:ssl] ? SSL_PORT : PORT) @tag_prefix = "RUBY" @tagno = 0 + @open_timeout = options[:open_timeout] || 30 + @read_timeout = options[:read_timeout] || 60 @parser = ResponseParser.new - @sock = TCPSocket.open(@host, @port) + @sock = tcp_socket(@host, @port) begin if options[:ssl] start_tls_session(options[:ssl]) @@ -1117,6 +1132,13 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L1132 end end + def tcp_socket(host, port) + Socket.tcp(host, port, :connect_timeout => @open_timeout) + rescue Errno::ETIMEDOUT + raise Net::OpenTimeout, "Timeout to open TCP connection to " + + "#{host}:#{port} (exceeds #{@open_timeout} seconds)" + end + def receive_responses connection_closed = false until connection_closed @@ -1199,14 +1221,35 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L1221 end end + def get_response_data(length, terminator = nil) + str = nil + buff = String.new + while true + str = @sock.read_nonblock(length, :exception => false) + case str + when :wait_readable + @sock.to_io.wait_readable(@read_timeout) or + raise Net::ReadTimeout, "#{@host}:#{@port} read timeout (exceeds #{@read_timeout} seconds)" + when nil + break + else + buff.concat(str) + if terminator ? buff.include?(terminator) : (buff.length >= length) + break + end + end + end + buff + end + def get_response buff = String.new while true - s = @sock.gets(CRLF) - break unless s + s = get_response_data(1, CRLF) + break if s.length == 0 buff.concat(s) if /\{(\d+)\}\r\n/n =~ s - s = @sock.read($1.to_i) + s = get_response_data($1.to_i) buff.concat(s) else break @@ -1487,7 +1530,7 @@ module Net https://github.com/ruby/ruby/blob/trunk/lib/net/imap.rb#L1530 end @sock = SSLSocket.new(@sock, context) @sock.sync_close = true - @sock.connect + ssl_socket_connect(@sock, @open_timeout) if context.verify_mode != VERIFY_NONE @sock.post_connection_check(@host) end -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/