# frozen_string_literal: true
# Copyright (c) 1999-2004 Yukihiro Matsumoto
# Copyright (c) 1999-2004 Minero Aoki
# written and maintained by Minero Aoki <aamine@loveruby.net>
# This program is free software. You can re-distribute and/or
# modify this program under the same terms as Ruby itself,
# Ruby Distribute License or GNU General Public License.
# WARNING: This file is going to remove.
# Do not rely on the implementation written in this file.
class Protocol #:nodoc: internal use only
def Protocol.protocol_param(name, val)
module_eval(<<-End, __FILE__, __LINE__ + 1)
def ssl_socket_connect(s, timeout)
raise Net::OpenTimeout if timeout <= 0
start = Process.clock_gettime Process::CLOCK_MONOTONIC
# to_io is required because SSLSocket doesn't have wait_readable yet
case s.connect_nonblock(exception: false)
when :wait_readable; s.to_io.wait_readable(timeout)
when :wait_writable; s.to_io.wait_writable(timeout)
timeout -= Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
class ProtocolError < StandardError; end
class ProtoSyntaxError < ProtocolError; end
class ProtoFatalError < ProtocolError; end
class ProtoUnknownError < ProtocolError; end
class ProtoServerError < ProtocolError; end
class ProtoAuthError < ProtocolError; end
class ProtoCommandError < ProtocolError; end
class ProtoRetriableError < ProtocolError; end
ProtocRetryError = ProtoRetriableError
# OpenTimeout, a subclass of Timeout::Error, is raised if a connection cannot
# be created within the open_timeout.
class OpenTimeout < Timeout::Error; end
# ReadTimeout, a subclass of Timeout::Error, is raised if a chunk of the
# response cannot be read within the read_timeout.
class ReadTimeout < Timeout::Error
msg = "#{msg} with #{@io.inspect}"
# WriteTimeout, a subclass of Timeout::Error, is raised if a chunk of the
# response cannot be written within the write_timeout. Not raised on Windows.
class WriteTimeout < Timeout::Error
msg = "#{msg} with #{@io.inspect}"
class BufferedIO #:nodoc: internal use only
def initialize(io, read_timeout: 60, write_timeout: 60, continue_timeout: nil, debug_output: nil)
@read_timeout = read_timeout
@write_timeout = write_timeout
@continue_timeout = continue_timeout
@debug_output = debug_output
attr_accessor :read_timeout
attr_accessor :write_timeout
attr_accessor :continue_timeout
attr_accessor :debug_output
"#<#{self.class} io=#{@io}>"
def read(len, dest = ''.b, ignore_eof = false)
LOG "reading #{len} bytes..."
while read_bytes + @rbuf.size < len
s = rbuf_consume(@rbuf.size)
s = rbuf_consume(len - read_bytes)
LOG "read #{read_bytes} bytes"
def read_all(dest = ''.b)
s = rbuf_consume(@rbuf.size)
LOG "read #{read_bytes} bytes"
def readuntil(terminator, ignore_eof = false)
until idx = @rbuf.index(terminator)
return rbuf_consume(idx + terminator.size)
return rbuf_consume(@rbuf.size)
tmp = @rbuf.empty? ? @rbuf : nil
case rv = @io.read_nonblock(BUFSIZE, tmp, exception: false)
(io = @io.to_io).wait_readable(@read_timeout) or raise Net::ReadTimeout.new(io)
# OpenSSL::Buffering#read_nonblock may fail with IO::WaitWritable.
# http://www.openssl.org/support/faq.html#PROG10
(io = @io.to_io).wait_writable(@read_timeout) or raise Net::ReadTimeout.new(io)
raise EOFError, 'end of file reached'
@debug_output << %Q[-> #{s.dump}\n] if @debug_output
@debug_output << '<- ' if @debug_output
@debug_output << "\n" if @debug_output
@debug_output << strs.map(&:dump).join if @debug_output
orig_written_bytes = @written_bytes
strs.each_with_index do |str, i|
case len = @io.write_nonblock(str, exception: false)
return @written_bytes - orig_written_bytes
str = str.byteslice(len, -len)
(io = @io.to_io).wait_writable(@write_timeout) or raise Net::WriteTimeout.new(io)
@save_debug_out = @debug_output
@debug_output = @save_debug_out
return unless @debug_output
@debug_output << msg + "\n"
class InternetMessageIO < BufferedIO #:nodoc: internal use only
while (line = readuntil("\r\n")) != ".\r\n"
yield line.delete_prefix('.')
LOG "read message (#{read_bytes} bytes)"
# *library private* (cannot handle 'break')
while (str = readuntil("\r\n")) != ".\r\n"
each_crlf_line(src) do |line|
LOG "writing message from #{src.class}"
def write_message_by_block(&block)
LOG 'writing message from block'
block.call(WriteAdapter.new(self, :write_message_0))
# allow `break' from writer block
if not @wbuf.empty? # unterminated last line
write0 dot_stuff(@wbuf.chomp) + "\r\n"
elsif @written_bytes == 0 # empty src
buffer_filling(@wbuf, src) do
while line = @wbuf.slice!(/\A[^\r\n]*(?:\n|\r(?:\n|(?!\z)))/)
yield line.chomp("\n") + "\r\n"
def buffer_filling(buf, src)
when String # for speeding up.
0.step(src.size - 1, 1024) do |i|
when File # for speeding up.
# The writer adapter class
def initialize(socket, method)
"#<#{self.class} socket=#{@socket.inspect}>"
@socket.__send__(@method_id, str)
write str.chomp("\n") + "\n"
class ReadAdapter #:nodoc: internal use only
call_block(str, &@block) if @block
# This method is needed because @block must be called by yield,
# not Proc#call. You can see difference when using `break' in