summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormatz <matz@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>1999-09-22 07:32:33 +0000
committermatz <matz@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>1999-09-22 07:32:33 +0000
commitd426749ff0ec0348e8b2f9e32724ac715aad500b (patch)
tree708a2d4c755687e4e397a0fc8300838e8fc93c2f
parent8aad024e3ac6524f8bc09d839a331f926eab30ec (diff)
maillib-1.0.1
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@535 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--lib/net/pop.rb230
-rw-r--r--lib/net/session.rb433
-rw-r--r--lib/net/smtp.rb128
3 files changed, 791 insertions, 0 deletions
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 <aamine@dp.u-netsurf.ne.jp>
+#
+
+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 <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
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 <aamine@dp.u-netsurf.ne.jp>
+#
+
+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