=begin = net/protocol.rb version 1.2.0 Copyright (C) 1999-2001 Yukihiro Matsumoto written & maintained by Minero Aoki 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 alias << write 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 << % 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