summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authoraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2003-07-02 02:34:39 +0000
committeraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2003-07-02 02:34:39 +0000
commitc20ecb1ba48fde8e81701f1365b738625c25582d (patch)
tree724812e1d9e3b330224fd0ac37f41994a027cafd /lib
parentf473140e62d438e845c9d1d9b62985f970a9a27a (diff)
* lib/net/smtp.rb: unify SMTP and SMTPCommand.
* lib/net/smtp.rb: new exception class SMTPError. * lib/net/smtp.rb: new exception class SMTPAuthenticationError. * lib/net/smtp.rb: new exception class SMTPServerBusy. * lib/net/smtp.rb: new exception class SMTPSyntaxError. * lib/net/smtp.rb: new exception class SMTPFatalError. * lib/net/smtp.rb: new exception class SMTPUnknownError. * lib/net/smtp.rb: change critical section protect algorithm. * lib/net/smtp.rb (SMTP#do_start): check authentication args before all. * lib/net/smtp.rb: new method send_message (alias send_mail). * lib/net/smtp.rb: new method open_message_stream (alias ready). * lib/net/pop.rb: POPBadResponse is a POPError. * lib/net/pop.rb (POPMail#pop): ban ReadAdapter. * lib/net/pop.rb (POPMail#top): ditto. * lib/net/pop.rb (POP3Command): change critical section protect algorithm. * lib/net/pop.rb (POP3Command#auth): USER and PASS should be one critical block. * lib/net/pop.rb (POP3Command#retr): ban `dest' argument using iterator. * lib/net/pop.rb (POP3Command#top): ditto. * lib/net/protocol.rb: #read_message_to -> #each_message_chunk * lib/net/protocol.rb: #D -> #LOG * lib/net/protocol.rb: #D_off -> #LOG_off * lib/net/protocol.rb: #D_on -> #LOG_on git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4026 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib')
-rw-r--r--lib/net/pop.rb128
-rw-r--r--lib/net/protocol.rb302
-rw-r--r--lib/net/smtp.rb255
3 files changed, 358 insertions, 327 deletions
diff --git a/lib/net/pop.rb b/lib/net/pop.rb
index 4fe33337cf..67542d794e 100644
--- a/lib/net/pop.rb
+++ b/lib/net/pop.rb
@@ -390,7 +390,7 @@ module Net
class POPError < ProtocolError; end
class POPAuthenticationError < ProtoAuthError; end
- class POPBadResponse < StandardError; end
+ class POPBadResponse < POPError; end
class POP3 < Protocol
@@ -405,6 +405,7 @@ module Net
110
end
+ # obsolete
def POP3.socket_type
Net::InternetMessageIO
end
@@ -421,7 +422,7 @@ module Net
account = nil, password = nil,
isapop = false, &block )
start(address, port, account, password, isapop) {|pop|
- pop.each_mail(&block)
+ pop.each_mail(&block)
}
end
@@ -429,7 +430,7 @@ module Net
account = nil, password = nil,
isapop = false, &block )
start(address, port, account, password, isapop) {|pop|
- pop.delete_all(&block)
+ pop.delete_all(&block)
}
end
@@ -442,7 +443,7 @@ module Net
def auth_only( account, password )
raise IOError, 'opening already opened POP session' if started?
start(account, password) {
- ;
+ ;
}
end
@@ -481,7 +482,7 @@ module Net
"#<#{self.class} #{@address}:#{@port} open=#{@started}>"
end
- def set_debug_output( arg ) # :nodoc:
+ def set_debug_output( arg )
@debug_output = arg
end
@@ -500,7 +501,7 @@ module Net
@started
end
- alias active? started? # backward compatibility
+ alias active? started? # obsolete
def start( account, password )
raise IOError, 'POP session already started' if @started
@@ -578,7 +579,7 @@ module Net
end
@mails = command().list.map {|num, size|
- POPMail.new(num, size, self, command())
+ POPMail.new(num, size, self, command())
}
@mails.dup
end
@@ -600,7 +601,7 @@ module Net
command().rset
mails().each do |m|
m.instance_eval {
- @deleted = false
+ @deleted = false
}
end
end
@@ -612,9 +613,9 @@ module Net
end
end
- end
+ end # class POP3
- # aliases
+ # class aliases
POP = POP3
POPSession = POP3
POP3Session = POP3
@@ -630,9 +631,9 @@ module Net
class POPMail
- def initialize( num, size, pop, cmd )
+ def initialize( num, len, pop, cmd )
@number = num
- @size = size
+ @length = len
@pop = pop
@command = cmd
@deleted = false
@@ -640,23 +641,37 @@ module Net
end
attr_reader :number
- attr_reader :size
+ attr_reader :length
+ alias size length
def inspect
"#<#{self.class} #{@number}#{@deleted ? ' deleted' : ''}>"
end
def pop( dest = '', &block )
- @command.retr(@number, (block ? ReadAdapter.new(block) : dest))
+ if block_given?
+ @command.retr(@number, &block)
+ nil
+ else
+ @command.retr(@number) do |chunk|
+ dest << chunk
+ end
+ dest
+ end
end
alias all pop # backward compatibility
alias mail pop # backward compatibility
+ # `dest' argument is obsolete
def top( lines, dest = '' )
- @command.top(@number, lines, dest)
+ @command.top(@number, lines) do |chunk|
+ dest << chunk
+ end
+ dest
end
+ # `dest' argument is obsolete
def header( dest = '' )
top(0, dest)
end
@@ -685,14 +700,14 @@ module Net
@uid = uid
end
- end
+ end # class POPMail
class POP3Command
def initialize( sock )
@socket = sock
- @in_critical_block = false
+ @error_occured = false
res = check_response(critical { recv_response() })
@apop_stamp = res.slice(/<.+>/)
end
@@ -702,30 +717,32 @@ module Net
end
def auth( account, password )
- check_response_auth(critical { get_response('USER ' + account) })
- check_response_auth(critical { get_response('PASS ' + password) })
+ check_response_auth(critical {
+ check_response_auth(get_response('USER ' + account))
+ get_response('PASS ' + password)
+ })
end
def apop( account, password )
raise POPAuthenticationError, 'not APOP server; cannot login' \
unless @apop_stamp
check_response_auth(critical {
- get_response('APOP %s %s',
- account,
- Digest::MD5.hexdigest(@apop_stamp + password))
+ get_response('APOP %s %s',
+ account,
+ Digest::MD5.hexdigest(@apop_stamp + password))
})
end
def list
critical {
- getok 'LIST'
- list = []
- @socket.each_list_item do |line|
- m = /\A(\d+)[ \t]+(\d+)/.match(line) or
- raise POPBadResponse, "bad response: #{line}"
- list.push [m[1].to_i, m[2].to_i]
- end
- list
+ getok 'LIST'
+ list = []
+ @socket.each_list_item do |line|
+ m = /\A(\d+)[ \t]+(\d+)/.match(line) or
+ raise POPBadResponse, "bad response: #{line}"
+ list.push [m[1].to_i, m[2].to_i]
+ end
+ return list
}
end
@@ -740,17 +757,17 @@ module Net
check_response(critical { get_response 'RSET' })
end
- def top( num, lines = 0, dest = '' )
+ def top( num, lines = 0, &block )
critical {
- getok('TOP %d %d', num, lines)
- @socket.read_message_to(dest)
+ getok('TOP %d %d', num, lines)
+ @socket.each_message_chunk(&block)
}
end
- def retr( num, dest = '' )
+ def retr( num, &block )
critical {
- getok('RETR %d', num)
- @socket.read_message_to dest
+ getok('RETR %d', num)
+ @socket.each_message_chunk(&block)
}
end
@@ -761,16 +778,16 @@ module Net
def uidl( num = nil )
if num
res = check_response(critical { get_response('UIDL %d', num) })
- res.split(/ /)[1]
+ return res.split(/ /)[1]
else
critical {
- getok('UIDL')
- table = {}
- @socket.each_list_item do |line|
- num, uid = line.split
- table[num.to_i] = uid
- end
- table
+ getok('UIDL')
+ table = {}
+ @socket.each_list_item do |line|
+ num, uid = line.split
+ table[num.to_i] = uid
+ end
+ return table
}
end
end
@@ -781,13 +798,13 @@ module Net
private
- def getok( *reqs )
- @socket.writeline sprintf(*reqs)
+ def getok( fmt, *fargs )
+ @socket.writeline sprintf(fmt, *fargs)
check_response(recv_response())
end
- def get_response( *reqs )
- @socket.writeline sprintf(*reqs)
+ def get_response( fmt, *fargs )
+ @socket.writeline sprintf(fmt, *fargs)
recv_response()
end
@@ -806,14 +823,15 @@ module Net
end
def critical
- return if @in_critical_block
- # Do not use ensure-block.
- @in_critical_block = true
- result = yield
- @in_critical_block = false
- result
+ return '+OK dummy ok response' if @error_occured
+ begin
+ return yield()
+ rescue Exception
+ @error_occured = true
+ raise
+ end
end
- end
+ end # class POP3Command
end # module Net
diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb
index b3ac5e5093..d33e93b266 100644
--- a/lib/net/protocol.rb
+++ b/lib/net/protocol.rb
@@ -52,24 +52,25 @@ module Net
alias open new
end
- def initialize( addr, port, otime = nil, rtime = nil, dout = nil )
+ def initialize( addr, port,
+ open_timeout = nil, read_timeout = nil,
+ debug_output = nil )
@address = addr
@port = port
- @read_timeout = rtime
- @debug_output = dout
-
- @socket = nil
- @rbuf = nil
-
- connect otime
- D 'opened'
+ @read_timeout = read_timeout
+ @debug_output = debug_output
+ @socket = nil
+ @rbuf = nil # read buffer
+ @wbuf = nil # write buffer
+ connect open_timeout
+ LOG 'opened'
end
attr_reader :address
attr_reader :port
def ip_address
- @socket or return ''
+ return '' unless @socket
@socket.addr[3]
end
@@ -77,10 +78,10 @@ module Net
attr_reader :socket
- def connect( otime )
- D "opening connection to #{@address}..."
- timeout(otime) {
- @socket = TCPsocket.new(@address, @port)
+ def connect( open_timeout )
+ LOG "opening connection to #{@address}..."
+ timeout(open_timeout) {
+ @socket = TCPsocket.new(@address, @port)
}
@rbuf = ''
end
@@ -89,19 +90,19 @@ module Net
def close
if @socket
@socket.close
- D 'closed'
+ LOG 'closed'
else
- D 'close call for already closed socket'
+ LOG 'close call for already closed socket'
end
@socket = nil
@rbuf = ''
end
- def reopen( otime = nil )
- D 'reopening...'
+ def reopen( open_timeout = nil )
+ LOG 'reopening...'
close
- connect otime
- D 'reopened'
+ connect open_timeout
+ LOG 'reopened'
end
def closed?
@@ -109,7 +110,7 @@ module Net
end
def inspect
- "#<#{self.class} #{closed? ? 'closed' : 'opened'}>"
+ "#<#{self.class} #{closed?() ? 'closed' : 'opened'}>"
end
###
@@ -118,74 +119,85 @@ module Net
public
- def read( len, dest = '', ignore = false )
- D_off "reading #{len} bytes..."
-
- rsize = 0
+ def read( len, dest = '', ignore_eof = false )
+ LOG "reading #{len} bytes..."
+ LOG_off()
+ read_bytes = 0
begin
- while rsize + @rbuf.size < len
- rsize += rbuf_moveto(dest, @rbuf.size)
+ while read_bytes + @rbuf.size < len
+ read_bytes += rbuf_moveto(dest, @rbuf.size)
rbuf_fill
end
- rbuf_moveto dest, len - rsize
+ rbuf_moveto dest, len - read_bytes
rescue EOFError
- raise unless ignore
+ raise unless ignore_eof
end
-
- D_on "read #{len} bytes"
+ LOG_on()
+ LOG "read #{read_bytes} bytes"
dest
end
def read_all( dest = '' )
- D_off 'reading all...'
-
- rsize = 0
+ LOG 'reading all...'
+ LOG_off()
+ read_bytes = 0
begin
while true
- rsize += rbuf_moveto(dest, @rbuf.size)
+ read_bytes += rbuf_moveto(dest, @rbuf.size)
rbuf_fill
end
rescue EOFError
;
end
-
- D_on "read #{rsize} bytes"
+ LOG_on()
+ LOG "read #{read_bytes} bytes"
dest
end
- def readuntil( target, ignore = false )
+ def readuntil( terminator, ignore_eof = false )
dest = ''
begin
- until idx = @rbuf.index(target)
+ until idx = @rbuf.index(terminator)
rbuf_fill
end
- rbuf_moveto dest, idx + target.size
+ rbuf_moveto dest, idx + terminator.size
rescue EOFError
- raise unless ignore
+ raise unless ignore_eof
rbuf_moveto dest, @rbuf.size
end
dest
end
def readline
- ret = readuntil("\n")
- ret.chop!
- ret
+ readuntil("\n").chop
end
- private
+ def each_message_chunk
+ LOG 'reading message...'
+ LOG_off()
+ read_bytes = 0
+ while (line = readuntil("\r\n")) != ".\r\n"
+ read_bytes += line.size
+ yield line.sub(/\A\./, '')
+ end
+ LOG_on()
+ LOG "read message (#{read_bytes} bytes)"
+ end
+
+ # *library private* (cannot handle 'break')
+ def each_list_item
+ while (str = readuntil("\r\n")) != ".\r\n"
+ yield str.chop
+ end
+ end
- BLOCK_SIZE = 1024
+ private
def rbuf_fill
- until IO.select [@socket], nil, nil, @read_timeout
- on_read_timeout
+ until IO.select([@socket], nil, nil, @read_timeout)
+ raise TimeoutError, "socket read timeout (#{@read_timeout} sec)"
end
- @rbuf << @socket.sysread(BLOCK_SIZE)
- end
-
- def on_read_timeout
- raise TimeoutError, "socket read timeout (#{@read_timeout} sec)"
+ @rbuf << @socket.sysread(1024)
end
def rbuf_moveto( dest, len )
@@ -194,173 +206,147 @@ module Net
len
end
- #
- # message read
- #
-
- public
-
- def read_message_to( dest )
- D_off 'reading text...'
-
- rsize = 0
- while (str = readuntil("\r\n")) != ".\r\n"
- rsize += str.size
- dest << str.sub(/\A\./, '')
- end
-
- D_on "read #{rsize} bytes"
- dest
- end
-
- # private use only (cannot handle 'break')
- def each_list_item
- while (str = readuntil("\r\n")) != ".\r\n"
- yield str.chop
- end
- end
-
-
###
### WRITE
###
- #
- # basic write
- #
-
public
def write( str )
writing {
- do_write str
+ write0 str
}
end
def writeline( str )
writing {
- do_write str + "\r\n"
+ write0 str + "\r\n"
}
end
+ def write_message( src )
+ LOG "writing message from #{src.class}"
+ LOG_off()
+ len = using_each_crlf_line {
+ write_message_0 src
+ }
+ LOG_on()
+ LOG "wrote #{len} bytes"
+ len
+ end
+
+ def write_message_by_block( &block )
+ LOG 'writing message from block'
+ LOG_off()
+ len = using_each_crlf_line {
+ begin
+ block.call(WriteAdapter.new(self, :write_message_0))
+ rescue LocalJumpError
+ # allow `break' from writer block
+ end
+ }
+ LOG_on()
+ LOG "wrote #{len} bytes"
+ len
+ end
+
private
def writing
- @writtensize = 0
+ @written_bytes = 0
@debug_output << '<- ' if @debug_output
yield
@socket.flush
@debug_output << "\n" if @debug_output
- @writtensize
+ bytes = @written_bytes
+ @written_bytes = nil
+ bytes
end
- def do_write( str )
+ def write0( str )
@debug_output << str.dump if @debug_output
- @writtensize += (n = @socket.write(str))
- n
+ len = @socket.write(str)
+ @written_bytes += len
+ len
end
#
- # message write
+ # Reads string from src calling :each, and write to @socket.
+ # Escapes '.' on the each line head.
#
-
- public
-
- def write_message( src )
- D_off "writing text from #{src.class}"
-
- wsize = using_each_crlf_line {
- wpend_in src
- }
-
- D_on "wrote #{wsize} bytes text"
- wsize
- end
-
- def through_message
- D_off 'writing text from block'
-
- wsize = using_each_crlf_line {
- yield WriteAdapter.new(self, :wpend_in)
- }
-
- D_on "wrote #{wsize} bytes text"
- wsize
- end
-
- private
-
- def wpend_in( src )
- line = nil
- pre = @writtensize
+ def write_message_0( src )
+ prev = @written_bytes
each_crlf_line(src) do |line|
- do_write '.' if line[0] == ?.
- do_write line
+ if line[0] == ?.
+ then write0 '.' + line
+ else write0 line
+ end
end
-
- @writtensize - pre
+ @written_bytes - prev
end
+ #
+ # setup @wbuf for each_crlf_line.
+ #
def using_each_crlf_line
writing {
@wbuf = ''
-
yield
-
if not @wbuf.empty? # unterminated last line
if @wbuf[-1] == ?\r
@wbuf.chop!
end
@wbuf.concat "\r\n"
- do_write @wbuf
- elsif @writtensize == 0 # empty src
- do_write "\r\n"
+ write0 @wbuf
+ elsif @written_bytes == 0 # empty src
+ write0 "\r\n"
end
- do_write ".\r\n"
-
+ write0 ".\r\n"
@wbuf = nil
}
end
+ #
+ # extract a CR-LF-terminating-line from @wbuf and yield it.
+ #
def each_crlf_line( src )
- str = m = beg = nil
-
adding(src) do
beg = 0
buf = @wbuf
while buf.index(/\n|\r\n|\r/, beg)
m = Regexp.last_match
- if m.begin(0) == buf.size - 1 and buf[-1] == ?\r
+ if (m.begin(0) == buf.length - 1) and buf[-1] == ?\r
# "...\r" : can follow "\n..."
break
end
- str = buf[ beg ... m.begin(0) ]
+ str = buf[beg ... m.begin(0)]
str.concat "\r\n"
yield str
beg = m.end(0)
end
- @wbuf = buf[ beg ... buf.size ]
+ @wbuf = buf[beg ... buf.length]
end
end
+ #
+ # Reads strings from SRC and add to @wbuf, then yield.
+ #
def adding( src )
- i = s = nil
-
case src
- when String
+ when String # for speeding up.
0.step(src.size - 1, 2048) do |i|
@wbuf << src[i,2048]
yield
end
- when File
+ when File # for speeding up.
while s = src.read(2048)
s[0,0] = @wbuf
@wbuf = s
yield
end
- else
+ else # generic reader
src.each do |s|
@wbuf << s
yield if @wbuf.size > 2048
@@ -375,18 +361,17 @@ module Net
private
- def D_off( msg )
- D msg
- @savedo, @debug_output = @debug_output, nil
+ def LOG_off
+ @save_debug_out = @debug_output
+ @debug_output = nil
end
- def D_on( msg )
- @debug_output = @savedo
- D msg
+ def LOG_on
+ @debug_output = @save_debug_out
end
- def D( msg )
- @debug_output or return
+ def LOG( msg )
+ return unless @debug_output
@debug_output << msg
@debug_output << "\n"
end
@@ -394,11 +379,14 @@ module Net
end
+ #
+ # The writer adapter class
+ #
class WriteAdapter
def initialize( sock, mid )
@socket = sock
- @mid = mid
+ @method_id = mid
end
def inspect
@@ -406,7 +394,7 @@ module Net
end
def write( str )
- @socket.__send__ @mid, str
+ @socket.__send__(@method_id, str)
end
alias print write
@@ -427,6 +415,9 @@ module Net
end
+ #
+ # The reader adapter class for internal use only.
+ #
class ReadAdapter
def initialize( block )
@@ -443,6 +434,11 @@ module Net
private
+ #
+ # This method is needed because @block must be called by yield,
+ # not Proc#call. You can see difference when using `break' in
+ # the block.
+ #
def call_block( str )
yield str
end
diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb
index d944f4710e..6329d0731d 100644
--- a/lib/net/smtp.rb
+++ b/lib/net/smtp.rb
@@ -249,7 +249,28 @@ require 'digest/md5'
module Net
- class SMTP < Protocol
+ module SMTPError
+ # This *class* is module for some reason.
+ # In ruby 1.9.x, this module becomes a class.
+ end
+ class SMTPAuthenticationError < ProtoAuthError
+ include SMTPError
+ end
+ class SMTPServerBusy < ProtoServerError
+ include SMTPError
+ end
+ class SMTPSyntaxError < ProtoSyntaxError
+ include SMTPError
+ end
+ class SMTPFatalError < ProtoFatalError
+ include SMTPError
+ end
+ class SMTPUnknownError < ProtoUnknownError
+ include SMTPError
+ end
+
+
+ class SMTP
Revision = %q$Revision$.split[1]
@@ -259,21 +280,18 @@ module Net
def initialize( address, port = nil )
@address = address
- @port = port || SMTP.default_port
-
+ @port = (port || SMTP.default_port)
@esmtp = true
-
- @command = nil
@socket = nil
@started = false
@open_timeout = 30
@read_timeout = 60
-
+ @error_occured = false
@debug_output = nil
end
def inspect
- "#<#{self.class} #{address}:#{@port} open=#{@started}>"
+ "#<#{self.class} #{@address}:#{@port} started=#{@started}>"
end
def esmtp?
@@ -318,7 +336,6 @@ module Net
def start( helo = 'localhost.localdomain',
user = nil, secret = nil, authtype = nil )
- raise IOError, 'SMTP session already started' if @started
if block_given?
begin
do_start(helo, user, secret, authtype)
@@ -332,98 +349,98 @@ module Net
end
end
- def do_start( helo, user, secret, authtype )
+ def do_start( helodomain, user, secret, authtype )
+ raise IOError, 'SMTP session already started' if @started
+ check_auth_args user, secret, authtype if user or secret
+
@socket = InternetMessageIO.open(@address, @port,
@open_timeout, @read_timeout,
@debug_output)
- @command = SMTPCommand.new(@socket)
+ check_response(critical { recv_response() })
begin
if @esmtp
- @command.ehlo helo
+ ehlo helodomain
else
- @command.helo helo
+ helo helodomain
end
rescue ProtocolError
if @esmtp
@esmtp = false
- @command = SMTPCommand.new(@socket)
+ @error_occured = false
retry
end
raise
end
-
- if user or secret
- raise ArgumentError, 'both of account and password are required'\
- unless user and secret
- mid = 'auth_' + (authtype || 'cram_md5').to_s
- raise ArgumentError, "wrong auth type #{authtype}"\
- unless command().respond_to?(mid)
- @command.__send__ mid, user, secret
- end
+ authenticate user, secret, authtype if user
end
private :do_start
def finish
raise IOError, 'closing already closed SMTP session' unless @started
- @command.quit if @command
- @command = nil
+ quit if @socket and not @socket.closed? and not @error_occured
@socket.close if @socket and not @socket.closed?
@socket = nil
+ @error_occured = false
@started = false
end
#
- # SMTP wrapper
+ # message send
#
- def send_mail( mailsrc, from_addr, *to_addrs )
- do_ready from_addr, to_addrs.flatten
- command().write_mail mailsrc
+ public
+
+ def send_message( msgstr, from_addr, *to_addrs )
+ send0(from_addr, to_addrs.flatten) {
+ @socket.write_message msgstr
+ }
end
- alias sendmail send_mail # backward compatibility
+ alias send_mail send_message
+ alias sendmail send_message # obsolete
- def ready( from_addr, *to_addrs, &block )
- do_ready from_addr, to_addrs.flatten
- command().through_mail(&block)
+ def open_message_stream( from_addr, *to_addrs, &block )
+ send0(from_addr, to_addrs.flatten) {
+ @socket.write_message_by_block(&block)
+ }
end
+ alias ready open_message_stream # obsolete
+
private
- def do_ready( from_addr, to_addrs )
+ def send0( from_addr, to_addrs )
+ raise IOError, "closed session" unless @socket
raise ArgumentError, 'mail destination does not given' if to_addrs.empty?
- command().mailfrom from_addr
- command().rcpt to_addrs
- end
- def command
- raise IOError, "closed session" unless @command
- @command
+ mailfrom from_addr
+ to_addrs.each do |to|
+ rcptto to
+ end
+ res = critical {
+ check_response(get_response('DATA'), true)
+ yield
+ recv_response()
+ }
+ check_response(res)
end
- end
-
- SMTPSession = SMTP
-
-
- class SMTPCommand
+ #
+ # auth
+ #
- def initialize( sock )
- @socket = sock
- @in_critical_block = false
- check_response(critical { recv_response() })
- end
+ private
- def inspect
- "#<#{self.class} socket=#{@socket.inspect}>"
+ def check_auth_args( user, secret, authtype )
+ raise ArgumentError, 'both of user and secret are required'\
+ unless user and secret
+ auth_method = "auth_#{authtype || 'cram_md5'}"
+ raise ArgumentError, "wrong auth type #{authtype}"\
+ unless respond_to?(auth_method)
end
- def helo( domain )
- getok('HELO %s', domain)
- end
-
- def ehlo( domain )
- getok('EHLO %s', domain)
+ def authenticate( user, secret, authtype )
+ __send__("auth_#{authtype || 'cram_md5'}", user, secret)
end
def auth_plain( user, secret )
@@ -434,9 +451,9 @@ module Net
def auth_login( user, secret )
res = critical {
- check_response(get_response('AUTH LOGIN'), true)
- check_response(get_response(base64_encode(user)), true)
- get_response(base64_encode(secret))
+ check_response(get_response('AUTH LOGIN'), true)
+ check_response(get_response(base64_encode(user)), true)
+ get_response(base64_encode(secret))
}
raise SMTPAuthenticationError, res unless /\A2../ === res
end
@@ -445,20 +462,20 @@ module Net
# CRAM-MD5: [RFC2195]
res = nil
critical {
- res = check_response(get_response('AUTH CRAM-MD5'), true)
- challenge = res.split(/ /)[1].unpack('m')[0]
- secret = Digest::MD5.digest(secret) if secret.size > 64
-
- isecret = secret + "\0" * (64 - secret.size)
- osecret = isecret.dup
- 0.upto(63) do |i|
- isecret[i] ^= 0x36
- osecret[i] ^= 0x5c
- end
- tmp = Digest::MD5.digest(isecret + challenge)
- tmp = Digest::MD5.hexdigest(osecret + tmp)
-
- res = get_response(base64_encode(user + ' ' + tmp))
+ res = check_response(get_response('AUTH CRAM-MD5'), true)
+ challenge = res.split(/ /)[1].unpack('m')[0]
+ secret = Digest::MD5.digest(secret) if secret.size > 64
+
+ isecret = secret + "\0" * (64 - secret.size)
+ osecret = isecret.dup
+ 0.upto(63) do |i|
+ isecret[i] ^= 0x36
+ osecret[i] ^= 0x5c
+ end
+ tmp = Digest::MD5.digest(isecret + challenge)
+ tmp = Digest::MD5.hexdigest(osecret + tmp)
+
+ res = get_response(base64_encode(user + ' ' + tmp))
}
raise SMTPAuthenticationError, res unless /\A2../ === res
end
@@ -467,45 +484,45 @@ module Net
# expects "str" may not become too long
[str].pack('m').gsub(/\s+/, '')
end
- private :base64_encode
- def mailfrom( fromaddr )
- getok('MAIL FROM:<%s>', fromaddr)
+ #
+ # SMTP command dispatcher
+ #
+
+ private
+
+ def helo( domain )
+ getok('HELO %s', domain)
end
- def rcpt( toaddrs )
- toaddrs.each do |i|
- getok('RCPT TO:<%s>', i)
- end
+ def ehlo( domain )
+ getok('EHLO %s', domain)
end
- def write_mail( src )
- res = critical {
- check_response(get_response('DATA'), true)
- @socket.write_message src
- recv_response()
- }
- check_response(res)
+ def mailfrom( fromaddr )
+ getok('MAIL FROM:<%s>', fromaddr)
end
- def through_mail( &block )
- res = critical {
- check_response(get_response('DATA'), true)
- @socket.through_message(&block)
- recv_response()
- }
- check_response(res)
+ def rcptto( to )
+ getok('RCPT TO:<%s>', to)
end
def quit
getok('QUIT')
end
+ #
+ # row level library
+ #
+
private
def getok( fmt, *args )
- @socket.writeline sprintf(fmt, *args)
- check_response(critical { recv_response() })
+ res = critical {
+ @socket.writeline sprintf(fmt, *args)
+ recv_response()
+ }
+ return check_response(res)
end
def get_response( fmt, *args )
@@ -524,29 +541,29 @@ module Net
end
def check_response( res, allow_continue = false )
- etype = case res[0]
- when ?2 then nil
- when ?3 then allow_continue ? nil : ProtoUnknownError
- when ?4 then ProtoServerError
- when ?5 then
- case res[1]
- when ?0 then ProtoSyntaxError
- when ?3 then ProtoAuthError
- when ?5 then ProtoFatalError
- end
- end
- raise etype, res if etype
- res
+ return res if /\A2/ === res
+ return res if allow_continue and /\A354/ === res
+ err = case res
+ when /\A4/ then SMTPServerBusy
+ when /\A50/ then SMTPSyntaxError
+ when /\A55/ then SMTPFatalError
+ else SMTPUnknownError
+ end
+ raise err, res
+ end
+
+ def critical( &block )
+ return '200 dummy reply code' if @error_occured
+ begin
+ return yield()
+ rescue Exception
+ @error_occured = true
+ raise
+ end
end
- def critical
- return if @in_critical_block
- @in_critical_block = true
- result = yield()
- @in_critical_block = false
- result
- end
+ end # class SMTP
- end
+ SMTPSession = SMTP
end # module Net