diff --git a/lib/net/ber/ber_parser.rb b/lib/net/ber/ber_parser.rb index ea6c0788..3c5af2e9 100644 --- a/lib/net/ber/ber_parser.rb +++ b/lib/net/ber/ber_parser.rb @@ -25,6 +25,9 @@ module Net::BER::BERParser BuiltinSyntax = Net::BER.compile_syntax(:universal => universal, :context_specific => context) + # Public: specify the BER socket read timeouts, nil by default (no timeout). + attr_accessor :read_ber_timeout + ## # This is an extract of our BER object parsing to simplify our # understanding of how we parse basic BER object types. @@ -124,7 +127,7 @@ def parse_ber_object(syntax, id, data) # invalid BER length case. Because the "lengthlength" value was not used # inside of #read_ber, we no longer return it. def read_ber_length - n = getbyte + n = getbyte_nonblock if n <= 0x7f n @@ -134,10 +137,9 @@ def read_ber_length raise Net::BER::BerError, "Invalid BER length 0xFF detected." else v = 0 - read(n & 0x7f).each_byte do |b| + read_ber_nonblock(n & 0x7f).each_byte do |b| v = (v << 8) + b end - v end end @@ -157,7 +159,7 @@ def read_ber(syntax = nil) # from streams that don't block when we ask for more data (like # StringIOs). At it is, this can throw TypeErrors and other nasties. - id = getbyte or return nil # don't trash this value, we'll use it later + id = read_ber_id or return nil # don't trash this value, we'll use it later content_length = read_ber_length yield id, content_length if block_given? @@ -165,9 +167,46 @@ def read_ber(syntax = nil) if -1 == content_length raise Net::BER::BerError, "Indeterminite BER content length not implemented." else - data = read(content_length) + data = read_ber_nonblock(content_length) end parse_ber_object(syntax, id, data) end + + # Internal: Returns the BER message ID or nil. + def read_ber_id + getbyte_nonblock + end + private :read_ber_id + + # Internal: Replaces `getbyte` with nonblocking implementation. + def getbyte_nonblock + begin + read_nonblock(1).ord + rescue IO::WaitReadable + if IO.select([self], nil, nil, read_ber_timeout) + read_nonblock(1).ord + else + raise Net::LDAP::LdapError, "Timed out reading from the socket" + end + end + rescue EOFError + # nothing to read on the socket (StringIO) + nil + end + private :getbyte_nonblock + + # Internal: Read `len` bytes, respecting timeout. + def read_ber_nonblock(len) + begin + read_nonblock(len) + rescue IO::WaitReadable + if IO.select([self], nil, nil, read_ber_timeout) + read_nonblock(len) + else + raise Net::LDAP::LdapError, "Timed out reading from the socket" + end + end + end + private :read_ber_nonblock end diff --git a/lib/net/ldap/connection.rb b/lib/net/ldap/connection.rb index 6371f636..9942308f 100644 --- a/lib/net/ldap/connection.rb +++ b/lib/net/ldap/connection.rb @@ -21,6 +21,10 @@ def initialize(server) raise Net::LDAP::LdapError, "Connection to #{server[:host]} timed out." end + if server[:timeout] + @conn.read_ber_timeout = server[:timeout] + end + if server[:encryption] setup_encryption server[:encryption] end