From d426749ff0ec0348e8b2f9e32724ac715aad500b Mon Sep 17 00:00:00 2001 From: matz Date: Wed, 22 Sep 1999 07:32:33 +0000 Subject: maillib-1.0.1 git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@535 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/net/pop.rb | 230 ++++++++++++++++++++++++++++ lib/net/session.rb | 433 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/net/smtp.rb | 128 ++++++++++++++++ 3 files changed, 791 insertions(+) create mode 100644 lib/net/pop.rb create mode 100644 lib/net/session.rb create mode 100644 lib/net/smtp.rb (limited to 'lib/net') diff --git a/lib/net/pop.rb b/lib/net/pop.rb new file mode 100644 index 0000000000..bd4551571d --- /dev/null +++ b/lib/net/pop.rb @@ -0,0 +1,230 @@ +# +# pop.rb version 1.0.1 +# +# author: Minero Aoki +# + +require 'net/session' +require 'md5' + + +module Net + + class POP3Session < Session + + attr :mails + + def each() @mails.each{|m| yield m} end + + + private + + + def proto_initialize + @proto_type = POP3Command + @port = 110 + @mails = [].freeze + end + + + def do_start( acnt, pwd ) + @proto.auth( acnt, pwd ) + @mails = [] + @proto.list.each_with_index do |size,idx| + if size then + @mails.push POPMail.new( idx, size, @proto ) + end + end + @mails.freeze + end + + + def do_finish + @proto.quit + end + + + + class POPMail + + def initialize( idx, siz, pro ) + @num = idx + @size = siz + @proto = pro + + @deleted = false + end + + attr :size + + def all( dest = '' ) + @proto.retr( @num, dest ) + end + alias pop all + alias mail all + + def top( lines, dest = '' ) + @proto.top( @num, lines, dest ) + end + + def header( dest = '' ) + top( 0, dest ) + end + + def delete + @proto.dele( @num ) + @deleted = true + end + alias delete! delete + + def deleted? + @deleted + end + + end + + end + + + class APOPSession < POP3Session + + def proto_initialize + super + @proto_type = APOPCommand + end + + end + + + POPSession = POP3Session + POP3 = POP3Session + + + + class POP3Command < Command + + def auth( acnt, pass ) + @socket.writeline( 'USER ' + acnt ) + check_reply_auth + + @socket.writeline( 'PASS ' + pass ) + ret = check_reply_auth + + return ret + end + + + def list + @socket.writeline( 'LIST' ) + check_reply( SuccessCode ) + + arr = [] + @socket.read_pendlist do |line| + num, siz = line.split( / +/o ) + arr[ num.to_i ] = siz.to_i + end + + return arr + end + + + def rset + @socket.writeline( 'RSET' ) + check_reply( SuccessCode ) + end + + + def top( num, lines = 0, dest = '' ) + @socket.writeline( sprintf( 'TOP %d %d', num, lines ) ) + check_reply( SuccessCode ) + + return @socket.read_pendstr( dest ) + end + + + def retr( num, dest = '', &block ) + @socket.writeline( sprintf( 'RETR %d', num ) ) + check_reply( SuccessCode ) + + return @socket.read_pendstr( dest, &block ) + end + + + def dele( num ) + @socket.writeline( sprintf( 'DELE %s', num ) ) + check_reply( SuccessCode ) + end + + + + private + + + def do_quit + @socket.writeline( 'QUIT' ) + check_reply( SuccessCode ) + end + + + def check_reply_auth + begin + cod = check_reply( SuccessCode ) + rescue ProtocolError + raise ProtoAuthError, 'Fail to POP authentication' + end + + return cod + end + + + def get_reply + str = @socket.readline + + if /\A\+/ === str then + return SuccessCode.new( str[0,3], str[3, str.size - 3].strip ) + else + return ErrorCode.new( str[0,4], str[4, str.size - 4].strip ) + end + end + + end + + + + class APOPCommand < POP3Command + + def initialize( sock ) + rep = super( sock ) + + /<[^@]+@[^@>]+>/o === rep.msg + @stamp = $& + unless @stamp then + raise ProtoAuthError, "This is not APOP server: can't login" + end + end + + + def auth( acnt, pass ) + @socket.writeline( "APOP #{acnt} #{digest(@stamp + pass)}" ) + return check_reply_auth + end + + + def digest( str ) + temp = MD5.new( str ).digest + + ret = '' + temp.each_byte do |i| + ret << sprintf( '%02x', i ) + end + return ret + end + + end + + + unless Session::Version == '1.0.1' then + $stderr.puts "WARNING: wrong version of session.rb & pop.rb" + end + +end 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 +# + +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( < [ 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 diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb new file mode 100644 index 0000000000..9f534c20c0 --- /dev/null +++ b/lib/net/smtp.rb @@ -0,0 +1,128 @@ +# +# smtp.rb version 1.0.1 +# +# author Minero Aoki +# + +require 'net/session' + + +module Net + + class SMTPSession < Session + + def proto_initialize + @proto_type = SMTPCommand + @port = 25 + end + + def sendmail( mailsrc, fromaddr, toaddrs ) + @proto.mailfrom( fromaddr ) + @proto.rcpt( toaddrs ) + @proto.data + @proto.sendmail( mailsrc ) + end + + + private + + + def do_start( helodom = nil ) + unless helodom then + helodom = ENV[ 'HOSTNAME' ] + end + @proto.helo( helodom ) + end + + def do_finish + @proto.quit + end + + end + + SMTP = SMTPSession + + + + class SMTPCommand < Command + + def helo( fromdom ) + @socket.writeline( 'HELO ' << fromdom ) + check_reply( SuccessCode ) + end + + + def mailfrom( fromaddr ) + @socket.writeline( 'MAIL FROM:<' + fromaddr + '>' ) + check_reply( SuccessCode ) + end + + + def rcpt( toaddrs ) + toaddrs.each do |i| + @socket.writeline( 'RCPT TO:<' + i + '>' ) + check_reply( SuccessCode ) + end + end + + + def data + @socket.writeline( 'DATA' ) + check_reply( ContinueCode ) + end + + + def sendmail( mailsrc ) + @socket.write_pendstr( mailsrc ) + check_reply( SuccessCode ) + end + + + private + + + def do_quit + @socket.writeline( 'QUIT' ) + check_reply( SuccessCode ) + end + + + def get_reply + arr = read_reply + stat = arr[0][0,3] + + cls = UnknownCode + case stat[0] + when ?2 then cls = SuccessCode + when ?3 then cls = ContinueCode + when ?4 then cls = ServerBusyCode + when ?5 then + case stat[1] + when ?0 then cls = SyntaxErrorCode + when ?5 then cls = FatalErrorCode + end + end + + return cls.new( stat, arr.join('') ) + end + + + def read_reply + arr = [] + + while (str = @socket.readline)[3] == ?- do # ex: "210-..." + arr.push str + end + arr.push str + + return arr + end + + end + + + unless Session::Version == '1.0.1' then + $stderr.puts "WARNING: wrong version of session.rb & smtp.rb" + end + +end -- cgit v1.2.3