mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
1a7cb01d64
* lib/net/http.rb: add HTTPRequest#basic_auth. * lib/net/smtp.rb: raise if only account or password is given. * lib/net/protocol.rb: WriteAdapter#<< returns self. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@1243 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
877 lines
15 KiB
Ruby
877 lines
15 KiB
Ruby
=begin
|
|
|
|
= net/protocol.rb version 1.2.0
|
|
|
|
Copyright (C) 1999-2001 Yukihiro Matsumoto
|
|
|
|
written & maintained by Minero Aoki <aamine@dp.u-netsurf.ne.jp>
|
|
|
|
This program is free software. You can re-distribute and/or
|
|
modify this program under the same terms as Ruby itself,
|
|
GNU General Public License or Ruby License.
|
|
|
|
Japanese version of this document is in "net" full package.
|
|
You can get it from RAA (Ruby Application Archive). RAA is:
|
|
http://www.ruby-lang.org/en/raa.html
|
|
|
|
|
|
== Net::Protocol
|
|
|
|
the abstract class for some internet protocols
|
|
|
|
=== Super Class
|
|
|
|
Object
|
|
|
|
=== Class Methods
|
|
|
|
: new( address = 'localhost', port = nil )
|
|
This method Creates a new protocol object.
|
|
|
|
: start( address = 'localhost', port = nil, *protoargs )
|
|
: start( address = 'localhost', port = nil, *protoargs ) {|proto| .... }
|
|
This method creates a new Protocol object and opens a session.
|
|
equals to Net::Protocol.new( address, port ).start( *protoargs )
|
|
|
|
=== Methods
|
|
|
|
: address
|
|
the address of connecting server (FQDN).
|
|
|
|
: port
|
|
connecting port number
|
|
|
|
: start( *args )
|
|
: start( *args ) {|proto| .... }
|
|
This method starts protocol. If protocol was already started,
|
|
do nothing and returns false.
|
|
|
|
'*args' are specified in subclasses.
|
|
|
|
When is called with block, gives Protocol object to block and
|
|
close session when block finished.
|
|
|
|
: finish
|
|
This method ends protocol. If you call this method before protocol starts,
|
|
it only return false without doing anything.
|
|
|
|
: active?
|
|
true if session have been started
|
|
|
|
=end
|
|
|
|
require 'socket'
|
|
require 'timeout'
|
|
|
|
|
|
module Net
|
|
|
|
module NetPrivate
|
|
end
|
|
|
|
def self.net_private( &block )
|
|
::Net::NetPrivate.module_eval( &block )
|
|
end
|
|
|
|
|
|
class Protocol
|
|
|
|
Version = '1.2.0'
|
|
|
|
|
|
class << self
|
|
|
|
def start( address = 'localhost', port = nil, *args )
|
|
instance = new( address, port )
|
|
|
|
if block_given? then
|
|
instance.start( *args ) { yield instance }
|
|
else
|
|
instance.start( *args )
|
|
instance
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def protocol_param( name, val )
|
|
module_eval %-
|
|
def self.#{name.id2name}
|
|
#{val}
|
|
end
|
|
-
|
|
end
|
|
|
|
end
|
|
|
|
|
|
#
|
|
# sub-class requirements
|
|
#
|
|
# protocol_param command_type
|
|
# protocol_param port
|
|
#
|
|
# private method do_start (optional)
|
|
# private method do_finish (optional)
|
|
#
|
|
|
|
protocol_param :port, 'nil'
|
|
protocol_param :command_type, 'nil'
|
|
protocol_param :socket_type, '::Net::NetPrivate::Socket'
|
|
|
|
|
|
def initialize( addr = nil, port = nil )
|
|
@address = addr || 'localhost'
|
|
@port = port || type.port
|
|
|
|
@command = nil
|
|
@socket = nil
|
|
|
|
@active = false
|
|
|
|
@open_timeout = nil
|
|
@read_timeout = nil
|
|
|
|
@dout = nil
|
|
end
|
|
|
|
attr_reader :address
|
|
attr_reader :port
|
|
|
|
attr_reader :command
|
|
attr_reader :socket
|
|
|
|
attr_accessor :open_timeout
|
|
attr_accessor :read_timeout
|
|
|
|
def active?
|
|
@active
|
|
end
|
|
|
|
def set_debug_output( arg ) # un-documented
|
|
@dout = arg
|
|
end
|
|
|
|
alias set_pipe set_debug_output
|
|
|
|
def inspect
|
|
"#<#{type} #{address}:#{port} open=#{active?}>"
|
|
end
|
|
|
|
#
|
|
# open session
|
|
#
|
|
|
|
def start( *args )
|
|
return false if active?
|
|
|
|
if block_given? then
|
|
begin
|
|
_start args
|
|
yield self
|
|
ensure
|
|
finish
|
|
end
|
|
else
|
|
_start args
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def _start( args )
|
|
connect
|
|
do_start( *args )
|
|
@active = true
|
|
end
|
|
|
|
def connect
|
|
conn_socket @address, @port
|
|
conn_command @socket
|
|
on_connect
|
|
end
|
|
|
|
def re_connect
|
|
@socket.reopen @open_timeout
|
|
on_connect
|
|
end
|
|
|
|
def conn_socket( addr, port )
|
|
@socket = type.socket_type.open(
|
|
addr, port, @open_timeout, @read_timeout, @dout )
|
|
end
|
|
|
|
def conn_command( sock )
|
|
@command = type.command_type.new( sock )
|
|
end
|
|
|
|
def on_connect
|
|
end
|
|
|
|
def do_start
|
|
end
|
|
|
|
#
|
|
# close session
|
|
#
|
|
|
|
public
|
|
|
|
def finish
|
|
return false unless active?
|
|
|
|
do_finish if @command and not @command.critical?
|
|
disconnect
|
|
@active = false
|
|
true
|
|
end
|
|
|
|
private
|
|
|
|
def do_finish
|
|
@command.quit
|
|
end
|
|
|
|
def disconnect
|
|
@command = nil
|
|
if @socket and not @socket.closed? then
|
|
@socket.close
|
|
end
|
|
@socket = nil
|
|
on_disconnect
|
|
end
|
|
|
|
def on_disconnect
|
|
end
|
|
|
|
end
|
|
|
|
Session = Protocol
|
|
|
|
|
|
net_private {
|
|
|
|
class Response
|
|
|
|
def initialize( ctype, cno, msg )
|
|
@code_type = ctype
|
|
@code = cno
|
|
@message = msg
|
|
super()
|
|
end
|
|
|
|
attr_reader :code_type, :code, :message
|
|
alias msg message
|
|
|
|
def inspect
|
|
"#<#{type} #{code}>"
|
|
end
|
|
|
|
def error!( data = nil )
|
|
raise code_type.error_type.new( code + ' ' + Net.quote(msg), data )
|
|
end
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
class ProtocolError
|
|
|
|
def initialize( msg, data = nil )
|
|
super msg
|
|
@data = data
|
|
end
|
|
|
|
attr :data
|
|
|
|
def inspect
|
|
"#<#{type}>"
|
|
end
|
|
|
|
end
|
|
|
|
|
|
class Code
|
|
|
|
def initialize( paren, err )
|
|
@parents = paren
|
|
@err = err
|
|
|
|
@parents.push self
|
|
end
|
|
|
|
attr_reader :parents
|
|
|
|
def inspect
|
|
"#<#{type}>"
|
|
end
|
|
|
|
def error_type
|
|
@err
|
|
end
|
|
|
|
def ===( response )
|
|
response.code_type.parents.reverse_each {|i| return true if i == self }
|
|
false
|
|
end
|
|
|
|
def mkchild( err = nil )
|
|
type.new( @parents + [self], err || @err )
|
|
end
|
|
|
|
end
|
|
|
|
ReplyCode = Code.new( [], ProtoUnknownError )
|
|
InformationCode = ReplyCode.mkchild( ProtoUnknownError )
|
|
SuccessCode = ReplyCode.mkchild( ProtoUnknownError )
|
|
ContinueCode = ReplyCode.mkchild( ProtoUnknownError )
|
|
ErrorCode = ReplyCode.mkchild( ProtocolError )
|
|
SyntaxErrorCode = ErrorCode.mkchild( ProtoSyntaxError )
|
|
FatalErrorCode = ErrorCode.mkchild( ProtoFatalError )
|
|
ServerErrorCode = ErrorCode.mkchild( ProtoServerError )
|
|
AuthErrorCode = ErrorCode.mkchild( ProtoAuthError )
|
|
RetriableCode = ReplyCode.mkchild( ProtoRetriableError )
|
|
UnknownCode = ReplyCode.mkchild( ProtoUnknownError )
|
|
|
|
|
|
|
|
net_private {
|
|
|
|
class WriteAdapter
|
|
|
|
def initialize( sock, mid )
|
|
@sock = sock
|
|
@mid = mid
|
|
end
|
|
|
|
def inspect
|
|
"#<#{type}>"
|
|
end
|
|
|
|
def write( str )
|
|
@sock.__send__ @mid, str
|
|
end
|
|
|
|
def <<( str )
|
|
@sock.__send__ @mid, str
|
|
self
|
|
end
|
|
|
|
end
|
|
|
|
class ReadAdapter
|
|
|
|
def initialize( block )
|
|
@block = block
|
|
end
|
|
|
|
def inspect
|
|
"#<#{type}>"
|
|
end
|
|
|
|
def <<( str )
|
|
callblock( str, &@block ) if @block
|
|
end
|
|
|
|
private
|
|
|
|
def callblock( str )
|
|
begin
|
|
user_break = true
|
|
yield str
|
|
user_break = false
|
|
rescue Exception
|
|
user_break = false
|
|
raise
|
|
ensure
|
|
if user_break then
|
|
@block = nil
|
|
return # stop break
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Command
|
|
|
|
def initialize( sock )
|
|
@socket = sock
|
|
@last_reply = nil
|
|
@critical = false
|
|
end
|
|
|
|
attr_accessor :socket
|
|
attr_reader :last_reply
|
|
|
|
def inspect
|
|
"#<#{type}>"
|
|
end
|
|
|
|
# abstract quit
|
|
|
|
|
|
private
|
|
|
|
# abstract get_reply()
|
|
|
|
def check_reply( *oks )
|
|
@last_reply = get_reply
|
|
reply_must( @last_reply, *oks )
|
|
end
|
|
|
|
def reply_must( rep, *oks )
|
|
oks.each do |i|
|
|
if i === rep then
|
|
return rep
|
|
end
|
|
end
|
|
rep.error!
|
|
end
|
|
|
|
def getok( line, expect = SuccessCode )
|
|
@socket.writeline line
|
|
check_reply expect
|
|
end
|
|
|
|
|
|
#
|
|
# error handle
|
|
#
|
|
|
|
public
|
|
|
|
def critical?
|
|
@critical
|
|
end
|
|
|
|
def error_ok
|
|
@critical = false
|
|
end
|
|
|
|
|
|
private
|
|
|
|
def critical
|
|
@critical = true
|
|
ret = yield
|
|
@critical = false
|
|
ret
|
|
end
|
|
|
|
def begin_critical
|
|
ret = @critical
|
|
@critical = true
|
|
not ret
|
|
end
|
|
|
|
def end_critical
|
|
@critical = false
|
|
end
|
|
|
|
end
|
|
|
|
|
|
class Socket
|
|
|
|
def initialize( addr, port, otime = nil, rtime = nil, dout = nil )
|
|
@addr = addr
|
|
@port = port
|
|
|
|
@read_timeout = rtime
|
|
|
|
@debugout = dout
|
|
|
|
@socket = nil
|
|
@sending = ''
|
|
@buffer = ''
|
|
|
|
connect otime
|
|
D 'opened'
|
|
end
|
|
|
|
def connect( otime )
|
|
D "opening connection to #{@addr}..."
|
|
timeout( otime ) {
|
|
@socket = TCPsocket.new( @addr, @port )
|
|
}
|
|
end
|
|
private :connect
|
|
|
|
attr :pipe, true
|
|
|
|
class << self
|
|
alias open new
|
|
end
|
|
|
|
def inspect
|
|
"#<#{type} #{closed? ? 'closed' : 'opened'}>"
|
|
end
|
|
|
|
def reopen( otime = nil )
|
|
D 'reopening...'
|
|
close
|
|
connect otime
|
|
D 'reopened'
|
|
end
|
|
|
|
attr :socket, true
|
|
|
|
def close
|
|
if @socket then
|
|
@socket.close
|
|
D 'closed'
|
|
else
|
|
D 'close call for already closed socket'
|
|
end
|
|
@socket = nil
|
|
@buffer = ''
|
|
end
|
|
|
|
def closed?
|
|
not @socket
|
|
end
|
|
|
|
def address
|
|
@addr.dup
|
|
end
|
|
|
|
alias addr address
|
|
|
|
attr_reader :port
|
|
|
|
def ip_address
|
|
@socket or return ''
|
|
@socket.addr[3]
|
|
end
|
|
|
|
alias ipaddr ip_address
|
|
|
|
attr_reader :sending
|
|
|
|
|
|
#
|
|
# read
|
|
#
|
|
|
|
public
|
|
|
|
CRLF = "\r\n"
|
|
|
|
def read( len, dest = '', ignerr = false )
|
|
D_off "reading #{len} bytes..."
|
|
|
|
rsize = 0
|
|
begin
|
|
while rsize + @buffer.size < len do
|
|
rsize += rbuf_moveto( dest, @buffer.size )
|
|
rbuf_fill
|
|
end
|
|
rbuf_moveto dest, len - rsize
|
|
rescue EOFError
|
|
raise unless igneof
|
|
end
|
|
|
|
D_on "read #{len} bytes"
|
|
dest
|
|
end
|
|
|
|
def read_all( dest = '' )
|
|
D_off 'reading all...'
|
|
|
|
rsize = 0
|
|
begin
|
|
while true do
|
|
rsize += rbuf_moveto( dest, @buffer.size )
|
|
rbuf_fill
|
|
end
|
|
rescue EOFError
|
|
;
|
|
end
|
|
|
|
D_on "read #{rsize} bytes"
|
|
dest
|
|
end
|
|
|
|
def readuntil( target, igneof = false )
|
|
dest = ''
|
|
begin
|
|
while true do
|
|
idx = @buffer.index( target )
|
|
break if idx
|
|
rbuf_fill
|
|
end
|
|
rbuf_moveto dest, idx + target.size
|
|
rescue EOFError
|
|
raise unless igneof
|
|
rbuf_moveto dest, @buffer.size
|
|
end
|
|
dest
|
|
end
|
|
|
|
def readline
|
|
ret = readuntil( "\n" )
|
|
ret.chop!
|
|
ret
|
|
end
|
|
|
|
def read_pendstr( dest )
|
|
D_off 'reading text...'
|
|
|
|
rsize = 0
|
|
while (str = readuntil("\r\n")) != ".\r\n" do
|
|
rsize += str.size
|
|
str.gsub!( /\A\./, '' )
|
|
dest << str
|
|
end
|
|
|
|
D_on "read #{rsize} bytes"
|
|
dest
|
|
end
|
|
|
|
# private use only (can not handle 'break')
|
|
def read_pendlist
|
|
D_off 'reading list...'
|
|
|
|
str = nil
|
|
i = 0
|
|
while (str = readuntil("\r\n")) != ".\r\n" do
|
|
i += 1
|
|
str.chop!
|
|
yield str
|
|
end
|
|
|
|
D_on "read #{i} items"
|
|
end
|
|
|
|
|
|
private
|
|
|
|
|
|
READ_SIZE = 1024 * 4
|
|
|
|
def rbuf_fill
|
|
unless IO.select [@socket], nil, nil, @read_timeout then
|
|
on_read_timeout
|
|
end
|
|
@buffer << @socket.sysread( READ_SIZE )
|
|
end
|
|
|
|
def on_read_timeout
|
|
raise TimeoutError, "socket read timeout (#{@read_timeout} sec)"
|
|
end
|
|
|
|
def rbuf_moveto( dest, len )
|
|
bsi = @buffer.size
|
|
s = @buffer[ 0, len ]
|
|
dest << s
|
|
@buffer = @buffer[ len, bsi - len ]
|
|
|
|
@debugout << %<read "#{Net.quote s}"\n> if @debugout
|
|
len
|
|
end
|
|
|
|
|
|
#
|
|
# write interfece
|
|
#
|
|
|
|
public
|
|
|
|
def write( str )
|
|
writing {
|
|
do_write str
|
|
}
|
|
end
|
|
|
|
def writeline( str )
|
|
writing {
|
|
do_write str + "\r\n"
|
|
}
|
|
end
|
|
|
|
def write_bin( src, block )
|
|
writing {
|
|
if block then
|
|
block.call ::Net::NetPrivate::WriteAdapter.new( self, :do_write )
|
|
else
|
|
src.each do |bin|
|
|
do_write bin
|
|
end
|
|
end
|
|
}
|
|
end
|
|
|
|
def write_pendstr( src, block )
|
|
D_off "writing text from #{src.type}"
|
|
|
|
wsize = use_each_crlf_line {
|
|
if block then
|
|
block.call ::Net::NetPrivate::WriteAdapter.new( self, :wpend_in )
|
|
else
|
|
wpend_in src
|
|
end
|
|
}
|
|
|
|
D_on "wrote #{wsize} bytes text"
|
|
wsize
|
|
end
|
|
|
|
|
|
private
|
|
|
|
|
|
def wpend_in( src )
|
|
line = nil
|
|
pre = @writtensize
|
|
each_crlf_line( src ) do |line|
|
|
do_write '.' if line[0] == ?.
|
|
do_write line
|
|
end
|
|
|
|
@writtensize - pre
|
|
end
|
|
|
|
def use_each_crlf_line
|
|
writing {
|
|
@wbuf = ''
|
|
|
|
yield
|
|
|
|
if not @wbuf.empty? then # un-terminated last line
|
|
if @wbuf[-1] == ?\r then
|
|
@wbuf.chop!
|
|
end
|
|
@wbuf.concat "\r\n"
|
|
do_write @wbuf
|
|
elsif @writtensize == 0 then # empty src
|
|
do_write "\r\n"
|
|
end
|
|
do_write ".\r\n"
|
|
|
|
@wbuf = nil
|
|
}
|
|
end
|
|
|
|
def each_crlf_line( src )
|
|
str = m = beg = nil
|
|
|
|
adding( src ) do
|
|
beg = 0
|
|
buf = @wbuf
|
|
while buf.index( /\n|\r\n|\r/, beg ) do
|
|
m = Regexp.last_match
|
|
if m.begin(0) == buf.size - 1 and buf[-1] == ?\r then
|
|
# "...\r" : can follow "\n..."
|
|
break
|
|
end
|
|
str = buf[ beg ... m.begin(0) ]
|
|
str.concat "\r\n"
|
|
yield str
|
|
beg = m.end(0)
|
|
end
|
|
@wbuf = buf[ beg ... buf.size ]
|
|
end
|
|
end
|
|
|
|
def adding( src )
|
|
i = nil
|
|
|
|
case src
|
|
when String
|
|
0.step( src.size - 1, 2048 ) do |i|
|
|
@wbuf << src[i,2048]
|
|
yield
|
|
end
|
|
|
|
when File
|
|
while true do
|
|
i = src.read( 2048 )
|
|
break unless i
|
|
i[0,0] = @wbuf
|
|
@wbuf = i
|
|
yield
|
|
end
|
|
|
|
else
|
|
src.each do |i|
|
|
@wbuf << i
|
|
if @wbuf.size > 2048 then
|
|
yield
|
|
end
|
|
end
|
|
yield unless @wbuf.empty?
|
|
end
|
|
end
|
|
|
|
|
|
def writing
|
|
@writtensize = 0
|
|
@sending = ''
|
|
|
|
yield
|
|
|
|
if @debugout then
|
|
@debugout << 'write "'
|
|
@debugout << @sending
|
|
@debugout << "\"\n"
|
|
end
|
|
@socket.flush
|
|
@writtensize
|
|
end
|
|
|
|
def do_write( arg )
|
|
if @debugout or @sending.size < 128 then
|
|
@sending << Net.quote( arg )
|
|
else
|
|
@sending << '...' unless @sending[-1] == ?.
|
|
end
|
|
|
|
s = @socket.write( arg )
|
|
@writtensize += s
|
|
s
|
|
end
|
|
|
|
|
|
def D_off( msg )
|
|
D msg
|
|
@savedo, @debugout = @debugout, nil
|
|
end
|
|
|
|
def D_on( msg )
|
|
@debugout = @savedo
|
|
D msg
|
|
end
|
|
|
|
def D( msg )
|
|
@debugout or return
|
|
@debugout << msg
|
|
@debugout << "\n"
|
|
end
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
def Net.quote( str )
|
|
str = str.gsub( "\n", '\\n' )
|
|
str.gsub!( "\r", '\\r' )
|
|
str.gsub!( "\t", '\\t' )
|
|
str
|
|
end
|
|
|
|
end # module Net
|