summaryrefslogtreecommitdiff
path: root/lib/net/session.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/net/session.rb')
-rw-r--r--lib/net/session.rb433
1 files changed, 433 insertions, 0 deletions
diff --git a/lib/net/session.rb b/lib/net/session.rb
new file mode 100644
index 0000000000..b0977e7e14
--- /dev/null
+++ b/lib/net/session.rb
@@ -0,0 +1,433 @@
+#
+# session.rb version 1.0.1
+#
+# author: Minero Aoki <aamine@dp.u-netsurf.ne.jp>
+#
+
+require 'socket'
+
+
+class String
+
+ def doquote
+ str = self.gsub( "\n", '\\n' )
+ str.gsub!( "\r", '\\r' )
+ str.gsub!( "\t", '\\t' )
+ return str
+ end
+
+end
+
+
+
+module Net
+
+ DEBUG = $DEBUG
+ # DEBUG = false
+
+
+ class Session
+
+ Version = '1.0.1'
+
+ def initialize( addr = 'localhost', port = nil )
+ proto_initialize
+ @address = addr
+ @port = port if port
+ @active = false
+ end
+
+ class << self
+ def start( address = 'localhost', port = nil, *args )
+ inst = new( address, port )
+ ret = inst.start( *args )
+
+ if iterator? then
+ ret = yield( inst )
+ inst.finish
+ end
+ return ret
+ end
+ end
+
+
+ attr :address
+ attr :port
+
+ attr :socket
+
+ attr :proto_type
+ attr :proto, true
+
+ def start( *args )
+ return false if active?
+
+ if ProtocolSocket === args[0] then
+ @socket = args.shift
+ else
+ @socket = ProtocolSocket.open( @address, @port )
+ end
+ @proto = @proto_type.new( @socket )
+ do_start( *args )
+
+ @active = true
+ end
+
+ def finish
+ if @proto then
+ do_finish
+ @proto = nil
+
+ return true
+ else
+ return false
+ end
+ end
+
+ def active?() @active end
+
+ end
+
+
+
+ class Command
+
+ def initialize( sock )
+ @socket = sock
+ check_reply( SuccessCode )
+ end
+
+ attr :socket, true
+
+ def quit
+ if @socket and not @socket.closed? then
+ begin
+ do_quit
+ ensure
+ @socket.close unless @socket.closed?
+ @socket = nil
+ end
+ end
+ end
+
+ private
+
+ def check_reply( *oks )
+ rep = get_reply
+ oks.each do |i|
+ if i === rep then
+ return rep
+ end
+ end
+
+ rep.error! @socket.sending
+ 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 ReplyCode
+
+ def initialize( cod, mes )
+ @code = cod
+ @msg = mes
+ end
+
+ attr :code
+ attr :msg
+
+ def error!( sending )
+ err, tag = Errors[ self.type ]
+ mes = sprintf( <<MES, tag, @code, sending.doquote, @msg.doquote )
+
+%s: status %s
+writing string is:
+%s
+
+error message from server is:
+%s
+MES
+ raise err, mes
+ end
+
+ end
+
+ class SuccessCode < ReplyCode ; end
+ class ContinueCode < SuccessCode ; end
+ class ErrorCode < ReplyCode ; end
+ class SyntaxErrorCode < ErrorCode ; end
+ class FatalErrorCode < ErrorCode ; end
+ class ServerBusyCode < ErrorCode ; end
+ class UnknownCode < ReplyCode ; end
+
+ class ReplyCode
+ Errors = {
+ SuccessCode => [ ProtoUnknownError, 'unknown error' ],
+ ContinueCode => [ ProtoUnknownError, 'unknown error' ],
+ ErrorCode => [ ProtocolError, 'protocol error' ],
+ SyntaxErrorCode => [ ProtoSyntaxError, 'syntax error' ],
+ FatalErrorCode => [ ProtoFatalError, 'fatal error' ],
+ ServerBusyCode => [ ProtoServerError, 'probably server busy' ],
+ UnknownCode => [ ProtoUnknownError, 'unknown error' ]
+ }
+ end
+
+
+
+ class ProtocolSocket
+
+ def initialize( addr, port )
+ @address = addr
+ @port = port
+
+ @ipaddr = ''
+ @closed = false
+ @sending = ''
+ @buffer = ''
+
+ @socket = TCPsocket.new( addr, port )
+ @ipaddr = @socket.addr[3]
+
+ @dout = Net::DEBUG
+ end
+
+ class << self
+ alias open new
+ end
+
+
+ attr :socket, true
+
+ def close
+ @socket.close
+ @closed = true
+ end
+
+ def closed?() @closed end
+
+ def addr() @address.dup end
+ def port() @port end
+ def ipaddr() @ipaddr.dup end
+
+ attr :sending
+
+
+ CRLF = "\r\n"
+ D_CRLF = ".\r\n"
+ TERMEXP = /\n|\r\n|\r/o
+
+
+ def read( len, ret = '' )
+ rsize = 0
+
+ while rsize + @buffer.size < len do
+ rsize += @buffer.size
+ ret << fetch_rbuf( @buffer.size )
+ fill_rbuf
+ end
+ ret << fetch_rbuf( len - rsize )
+
+ return ret
+ end
+
+
+ def readuntil( target )
+ until idx = @buffer.index( target ) do
+ fill_rbuf
+ end
+
+ return fetch_rbuf( idx + target.size )
+ end
+
+
+ def readline
+ ret = readuntil( CRLF )
+ ret.chop!
+ return ret
+ end
+
+
+ def read_pendstr( dest = '' )
+ $stderr.puts "reading pendstr" if pre = @dout ; @dout = false
+
+ rsize = 0
+
+ while (str = readuntil( CRLF )) != D_CRLF do
+ rsize += str.size
+ str.gsub!( /\A\./o, '' )
+ dest << str
+ end
+
+ $stderr.puts "read pendstr #{rsize} bytes" if @dout = pre
+ return dest
+ end
+
+
+ def read_pendlist
+ arr = []
+ str = nil
+ call = iterator?
+
+ while (str = readuntil( CRLF )) != D_CRLF do
+ str.chop!
+ arr.push str
+ yield str if iterator?
+ end
+
+ return arr
+ end
+
+
+ private
+
+
+ READ_BLOCK = 1024 * 8
+
+ def fill_rbuf
+ @buffer << @socket.sysread( READ_BLOCK )
+ end
+
+ def fetch_rbuf( len )
+ bsi = @buffer.size
+ ret = @buffer[ 0, len ]
+ @buffer = @buffer[ len, bsi - len ]
+
+ if @dout then
+ $stderr.print 'read "'
+ debugout ret
+ $stderr.print "\"\n"
+ end
+ return ret
+ end
+
+
+ ### write
+
+ public
+
+
+ def write( src )
+ do_write_beg
+ each_crlf_line( src ) do |line|
+ do_write_do line
+ end
+ return do_write_fin
+ end
+
+
+ def writebin( src )
+ do_write_beg
+ src.each do |bin|
+ do_write_do bin
+ end
+ return do_write_fin
+ end
+
+
+ def writeline( str )
+ do_write_beg
+ do_write_do str
+ do_write_do CRLF
+ return do_write_fin
+ end
+
+
+ def write_pendstr( src )
+ $stderr.puts "writing pendstr from #{src.type}" if pre = @dout
+ @dout = false
+
+ do_write_beg
+ each_crlf_line( src ) do |line|
+ do_write_do '.' if line[0] == ?.
+ do_write_do line
+ end
+ do_write_do D_CRLF
+ wsize = do_write_fin
+
+ $stderr.puts "wrote pendstr #{wsize} bytes" if @dout = pre
+ return wsize
+ end
+
+
+ private
+
+
+ def each_crlf_line( src )
+ buf = ''
+ beg = 0
+ pos = nil
+
+ src.each do |b|
+ buf << b
+
+ beg = 0
+ while (pos = buf.index(TERMEXP, beg)) and (pos < buf.size - 2) do
+ pos += $&.size
+ tmp = buf[ beg, pos - beg ]
+ tmp.chop!
+ yield tmp << CRLF
+ beg = pos
+ end
+ buf = buf[ beg, buf.size - beg ] if beg != 0
+ end
+
+ buf << "\n" unless /\n|\r/o === buf[-1,1]
+
+ beg = 0
+ while pos = buf.index(TERMEXP, beg) do
+ pos += $&.size
+ tmp = buf[ beg, pos - beg ]
+ tmp.chop!
+ yield tmp << CRLF
+ beg = pos
+ end
+ end
+
+
+ def do_write_beg
+ $stderr.print 'write "' if @dout
+
+ @writtensize = 0
+ @sending = ''
+ end
+
+ def do_write_do( arg )
+ debugout arg if @dout
+
+ if @sending.size < 128 then
+ @sending << arg
+ else
+ @sending << '...' unless @sending[-1] == ?.
+ end
+ s = @socket.write( arg )
+ @writtensize += s
+ return s
+ end
+
+ def do_write_fin
+ $stderr.puts if @dout
+
+ @socket.flush
+ return @writtensize
+ end
+
+
+ def debugout( ret )
+ while ret and tmp = ret[ 0, 50 ] do
+ ret = ret[ 50, ret.size - 50 ]
+ tmp = tmp.inspect
+ $stderr.print tmp[ 1, tmp.size - 2 ]
+ end
+ end
+
+ end
+
+end