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:
parent
8aad024e3a
commit
d426749ff0
230
lib/net/pop.rb
Normal file
230
lib/net/pop.rb
Normal 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
433
lib/net/session.rb
Normal 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
128
lib/net/smtp.rb
Normal 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
|
Loading…
x
Reference in New Issue
Block a user