maillib-1.0.1

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@535 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
matz 1999-09-22 07:32:33 +00:00
parent 8aad024e3a
commit d426749ff0
3 changed files with 791 additions and 0 deletions

230
lib/net/pop.rb Normal file
View File

@ -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

433
lib/net/session.rb Normal file
View File

@ -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

128
lib/net/smtp.rb Normal file
View File

@ -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