mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
* 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
This commit is contained in:
parent
f473140e62
commit
c20ecb1ba4
4 changed files with 410 additions and 329 deletions
50
ChangeLog
50
ChangeLog
|
@ -1,3 +1,53 @@
|
||||||
|
Wed Jul 2 11:39:50 2003 Minero Aoki <aamine@loveruby.net>
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
||||||
Wed Jul 2 11:10:47 2003 Minero Aoki <aamine@loveruby.net>
|
Wed Jul 2 11:10:47 2003 Minero Aoki <aamine@loveruby.net>
|
||||||
|
|
||||||
* lib/net/http.rb: set old class aliases for backward
|
* lib/net/http.rb: set old class aliases for backward
|
||||||
|
|
128
lib/net/pop.rb
128
lib/net/pop.rb
|
@ -390,7 +390,7 @@ module Net
|
||||||
|
|
||||||
class POPError < ProtocolError; end
|
class POPError < ProtocolError; end
|
||||||
class POPAuthenticationError < ProtoAuthError; end
|
class POPAuthenticationError < ProtoAuthError; end
|
||||||
class POPBadResponse < StandardError; end
|
class POPBadResponse < POPError; end
|
||||||
|
|
||||||
|
|
||||||
class POP3 < Protocol
|
class POP3 < Protocol
|
||||||
|
@ -405,6 +405,7 @@ module Net
|
||||||
110
|
110
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# obsolete
|
||||||
def POP3.socket_type
|
def POP3.socket_type
|
||||||
Net::InternetMessageIO
|
Net::InternetMessageIO
|
||||||
end
|
end
|
||||||
|
@ -421,7 +422,7 @@ module Net
|
||||||
account = nil, password = nil,
|
account = nil, password = nil,
|
||||||
isapop = false, &block )
|
isapop = false, &block )
|
||||||
start(address, port, account, password, isapop) {|pop|
|
start(address, port, account, password, isapop) {|pop|
|
||||||
pop.each_mail(&block)
|
pop.each_mail(&block)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -429,7 +430,7 @@ module Net
|
||||||
account = nil, password = nil,
|
account = nil, password = nil,
|
||||||
isapop = false, &block )
|
isapop = false, &block )
|
||||||
start(address, port, account, password, isapop) {|pop|
|
start(address, port, account, password, isapop) {|pop|
|
||||||
pop.delete_all(&block)
|
pop.delete_all(&block)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -442,7 +443,7 @@ module Net
|
||||||
def auth_only( account, password )
|
def auth_only( account, password )
|
||||||
raise IOError, 'opening already opened POP session' if started?
|
raise IOError, 'opening already opened POP session' if started?
|
||||||
start(account, password) {
|
start(account, password) {
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -481,7 +482,7 @@ module Net
|
||||||
"#<#{self.class} #{@address}:#{@port} open=#{@started}>"
|
"#<#{self.class} #{@address}:#{@port} open=#{@started}>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_debug_output( arg ) # :nodoc:
|
def set_debug_output( arg )
|
||||||
@debug_output = arg
|
@debug_output = arg
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -500,7 +501,7 @@ module Net
|
||||||
@started
|
@started
|
||||||
end
|
end
|
||||||
|
|
||||||
alias active? started? # backward compatibility
|
alias active? started? # obsolete
|
||||||
|
|
||||||
def start( account, password )
|
def start( account, password )
|
||||||
raise IOError, 'POP session already started' if @started
|
raise IOError, 'POP session already started' if @started
|
||||||
|
@ -578,7 +579,7 @@ module Net
|
||||||
end
|
end
|
||||||
|
|
||||||
@mails = command().list.map {|num, size|
|
@mails = command().list.map {|num, size|
|
||||||
POPMail.new(num, size, self, command())
|
POPMail.new(num, size, self, command())
|
||||||
}
|
}
|
||||||
@mails.dup
|
@mails.dup
|
||||||
end
|
end
|
||||||
|
@ -600,7 +601,7 @@ module Net
|
||||||
command().rset
|
command().rset
|
||||||
mails().each do |m|
|
mails().each do |m|
|
||||||
m.instance_eval {
|
m.instance_eval {
|
||||||
@deleted = false
|
@deleted = false
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -612,9 +613,9 @@ module Net
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end # class POP3
|
||||||
|
|
||||||
# aliases
|
# class aliases
|
||||||
POP = POP3
|
POP = POP3
|
||||||
POPSession = POP3
|
POPSession = POP3
|
||||||
POP3Session = POP3
|
POP3Session = POP3
|
||||||
|
@ -630,9 +631,9 @@ module Net
|
||||||
|
|
||||||
class POPMail
|
class POPMail
|
||||||
|
|
||||||
def initialize( num, size, pop, cmd )
|
def initialize( num, len, pop, cmd )
|
||||||
@number = num
|
@number = num
|
||||||
@size = size
|
@length = len
|
||||||
@pop = pop
|
@pop = pop
|
||||||
@command = cmd
|
@command = cmd
|
||||||
@deleted = false
|
@deleted = false
|
||||||
|
@ -640,23 +641,37 @@ module Net
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :number
|
attr_reader :number
|
||||||
attr_reader :size
|
attr_reader :length
|
||||||
|
alias size length
|
||||||
|
|
||||||
def inspect
|
def inspect
|
||||||
"#<#{self.class} #{@number}#{@deleted ? ' deleted' : ''}>"
|
"#<#{self.class} #{@number}#{@deleted ? ' deleted' : ''}>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def pop( dest = '', &block )
|
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
|
end
|
||||||
|
|
||||||
alias all pop # backward compatibility
|
alias all pop # backward compatibility
|
||||||
alias mail pop # backward compatibility
|
alias mail pop # backward compatibility
|
||||||
|
|
||||||
|
# `dest' argument is obsolete
|
||||||
def top( lines, dest = '' )
|
def top( lines, dest = '' )
|
||||||
@command.top(@number, lines, dest)
|
@command.top(@number, lines) do |chunk|
|
||||||
|
dest << chunk
|
||||||
|
end
|
||||||
|
dest
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# `dest' argument is obsolete
|
||||||
def header( dest = '' )
|
def header( dest = '' )
|
||||||
top(0, dest)
|
top(0, dest)
|
||||||
end
|
end
|
||||||
|
@ -685,14 +700,14 @@ module Net
|
||||||
@uid = uid
|
@uid = uid
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end # class POPMail
|
||||||
|
|
||||||
|
|
||||||
class POP3Command
|
class POP3Command
|
||||||
|
|
||||||
def initialize( sock )
|
def initialize( sock )
|
||||||
@socket = sock
|
@socket = sock
|
||||||
@in_critical_block = false
|
@error_occured = false
|
||||||
res = check_response(critical { recv_response() })
|
res = check_response(critical { recv_response() })
|
||||||
@apop_stamp = res.slice(/<.+>/)
|
@apop_stamp = res.slice(/<.+>/)
|
||||||
end
|
end
|
||||||
|
@ -702,30 +717,32 @@ module Net
|
||||||
end
|
end
|
||||||
|
|
||||||
def auth( account, password )
|
def auth( account, password )
|
||||||
check_response_auth(critical { get_response('USER ' + account) })
|
check_response_auth(critical {
|
||||||
check_response_auth(critical { get_response('PASS ' + password) })
|
check_response_auth(get_response('USER ' + account))
|
||||||
|
get_response('PASS ' + password)
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def apop( account, password )
|
def apop( account, password )
|
||||||
raise POPAuthenticationError, 'not APOP server; cannot login' \
|
raise POPAuthenticationError, 'not APOP server; cannot login' \
|
||||||
unless @apop_stamp
|
unless @apop_stamp
|
||||||
check_response_auth(critical {
|
check_response_auth(critical {
|
||||||
get_response('APOP %s %s',
|
get_response('APOP %s %s',
|
||||||
account,
|
account,
|
||||||
Digest::MD5.hexdigest(@apop_stamp + password))
|
Digest::MD5.hexdigest(@apop_stamp + password))
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def list
|
def list
|
||||||
critical {
|
critical {
|
||||||
getok 'LIST'
|
getok 'LIST'
|
||||||
list = []
|
list = []
|
||||||
@socket.each_list_item do |line|
|
@socket.each_list_item do |line|
|
||||||
m = /\A(\d+)[ \t]+(\d+)/.match(line) or
|
m = /\A(\d+)[ \t]+(\d+)/.match(line) or
|
||||||
raise POPBadResponse, "bad response: #{line}"
|
raise POPBadResponse, "bad response: #{line}"
|
||||||
list.push [m[1].to_i, m[2].to_i]
|
list.push [m[1].to_i, m[2].to_i]
|
||||||
end
|
end
|
||||||
list
|
return list
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -740,17 +757,17 @@ module Net
|
||||||
check_response(critical { get_response 'RSET' })
|
check_response(critical { get_response 'RSET' })
|
||||||
end
|
end
|
||||||
|
|
||||||
def top( num, lines = 0, dest = '' )
|
def top( num, lines = 0, &block )
|
||||||
critical {
|
critical {
|
||||||
getok('TOP %d %d', num, lines)
|
getok('TOP %d %d', num, lines)
|
||||||
@socket.read_message_to(dest)
|
@socket.each_message_chunk(&block)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def retr( num, dest = '' )
|
def retr( num, &block )
|
||||||
critical {
|
critical {
|
||||||
getok('RETR %d', num)
|
getok('RETR %d', num)
|
||||||
@socket.read_message_to dest
|
@socket.each_message_chunk(&block)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -761,16 +778,16 @@ module Net
|
||||||
def uidl( num = nil )
|
def uidl( num = nil )
|
||||||
if num
|
if num
|
||||||
res = check_response(critical { get_response('UIDL %d', num) })
|
res = check_response(critical { get_response('UIDL %d', num) })
|
||||||
res.split(/ /)[1]
|
return res.split(/ /)[1]
|
||||||
else
|
else
|
||||||
critical {
|
critical {
|
||||||
getok('UIDL')
|
getok('UIDL')
|
||||||
table = {}
|
table = {}
|
||||||
@socket.each_list_item do |line|
|
@socket.each_list_item do |line|
|
||||||
num, uid = line.split
|
num, uid = line.split
|
||||||
table[num.to_i] = uid
|
table[num.to_i] = uid
|
||||||
end
|
end
|
||||||
table
|
return table
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -781,13 +798,13 @@ module Net
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def getok( *reqs )
|
def getok( fmt, *fargs )
|
||||||
@socket.writeline sprintf(*reqs)
|
@socket.writeline sprintf(fmt, *fargs)
|
||||||
check_response(recv_response())
|
check_response(recv_response())
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_response( *reqs )
|
def get_response( fmt, *fargs )
|
||||||
@socket.writeline sprintf(*reqs)
|
@socket.writeline sprintf(fmt, *fargs)
|
||||||
recv_response()
|
recv_response()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -806,14 +823,15 @@ module Net
|
||||||
end
|
end
|
||||||
|
|
||||||
def critical
|
def critical
|
||||||
return if @in_critical_block
|
return '+OK dummy ok response' if @error_occured
|
||||||
# Do not use ensure-block.
|
begin
|
||||||
@in_critical_block = true
|
return yield()
|
||||||
result = yield
|
rescue Exception
|
||||||
@in_critical_block = false
|
@error_occured = true
|
||||||
result
|
raise
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end # class POP3Command
|
||||||
|
|
||||||
end # module Net
|
end # module Net
|
||||||
|
|
|
@ -52,24 +52,25 @@ module Net
|
||||||
alias open new
|
alias open new
|
||||||
end
|
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
|
@address = addr
|
||||||
@port = port
|
@port = port
|
||||||
@read_timeout = rtime
|
@read_timeout = read_timeout
|
||||||
@debug_output = dout
|
@debug_output = debug_output
|
||||||
|
@socket = nil
|
||||||
@socket = nil
|
@rbuf = nil # read buffer
|
||||||
@rbuf = nil
|
@wbuf = nil # write buffer
|
||||||
|
connect open_timeout
|
||||||
connect otime
|
LOG 'opened'
|
||||||
D 'opened'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :address
|
attr_reader :address
|
||||||
attr_reader :port
|
attr_reader :port
|
||||||
|
|
||||||
def ip_address
|
def ip_address
|
||||||
@socket or return ''
|
return '' unless @socket
|
||||||
@socket.addr[3]
|
@socket.addr[3]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -77,10 +78,10 @@ module Net
|
||||||
|
|
||||||
attr_reader :socket
|
attr_reader :socket
|
||||||
|
|
||||||
def connect( otime )
|
def connect( open_timeout )
|
||||||
D "opening connection to #{@address}..."
|
LOG "opening connection to #{@address}..."
|
||||||
timeout(otime) {
|
timeout(open_timeout) {
|
||||||
@socket = TCPsocket.new(@address, @port)
|
@socket = TCPsocket.new(@address, @port)
|
||||||
}
|
}
|
||||||
@rbuf = ''
|
@rbuf = ''
|
||||||
end
|
end
|
||||||
|
@ -89,19 +90,19 @@ module Net
|
||||||
def close
|
def close
|
||||||
if @socket
|
if @socket
|
||||||
@socket.close
|
@socket.close
|
||||||
D 'closed'
|
LOG 'closed'
|
||||||
else
|
else
|
||||||
D 'close call for already closed socket'
|
LOG 'close call for already closed socket'
|
||||||
end
|
end
|
||||||
@socket = nil
|
@socket = nil
|
||||||
@rbuf = ''
|
@rbuf = ''
|
||||||
end
|
end
|
||||||
|
|
||||||
def reopen( otime = nil )
|
def reopen( open_timeout = nil )
|
||||||
D 'reopening...'
|
LOG 'reopening...'
|
||||||
close
|
close
|
||||||
connect otime
|
connect open_timeout
|
||||||
D 'reopened'
|
LOG 'reopened'
|
||||||
end
|
end
|
||||||
|
|
||||||
def closed?
|
def closed?
|
||||||
|
@ -109,7 +110,7 @@ module Net
|
||||||
end
|
end
|
||||||
|
|
||||||
def inspect
|
def inspect
|
||||||
"#<#{self.class} #{closed? ? 'closed' : 'opened'}>"
|
"#<#{self.class} #{closed?() ? 'closed' : 'opened'}>"
|
||||||
end
|
end
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -118,74 +119,85 @@ module Net
|
||||||
|
|
||||||
public
|
public
|
||||||
|
|
||||||
def read( len, dest = '', ignore = false )
|
def read( len, dest = '', ignore_eof = false )
|
||||||
D_off "reading #{len} bytes..."
|
LOG "reading #{len} bytes..."
|
||||||
|
LOG_off()
|
||||||
rsize = 0
|
read_bytes = 0
|
||||||
begin
|
begin
|
||||||
while rsize + @rbuf.size < len
|
while read_bytes + @rbuf.size < len
|
||||||
rsize += rbuf_moveto(dest, @rbuf.size)
|
read_bytes += rbuf_moveto(dest, @rbuf.size)
|
||||||
rbuf_fill
|
rbuf_fill
|
||||||
end
|
end
|
||||||
rbuf_moveto dest, len - rsize
|
rbuf_moveto dest, len - read_bytes
|
||||||
rescue EOFError
|
rescue EOFError
|
||||||
raise unless ignore
|
raise unless ignore_eof
|
||||||
end
|
end
|
||||||
|
LOG_on()
|
||||||
D_on "read #{len} bytes"
|
LOG "read #{read_bytes} bytes"
|
||||||
dest
|
dest
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_all( dest = '' )
|
def read_all( dest = '' )
|
||||||
D_off 'reading all...'
|
LOG 'reading all...'
|
||||||
|
LOG_off()
|
||||||
rsize = 0
|
read_bytes = 0
|
||||||
begin
|
begin
|
||||||
while true
|
while true
|
||||||
rsize += rbuf_moveto(dest, @rbuf.size)
|
read_bytes += rbuf_moveto(dest, @rbuf.size)
|
||||||
rbuf_fill
|
rbuf_fill
|
||||||
end
|
end
|
||||||
rescue EOFError
|
rescue EOFError
|
||||||
;
|
;
|
||||||
end
|
end
|
||||||
|
LOG_on()
|
||||||
D_on "read #{rsize} bytes"
|
LOG "read #{read_bytes} bytes"
|
||||||
dest
|
dest
|
||||||
end
|
end
|
||||||
|
|
||||||
def readuntil( target, ignore = false )
|
def readuntil( terminator, ignore_eof = false )
|
||||||
dest = ''
|
dest = ''
|
||||||
begin
|
begin
|
||||||
until idx = @rbuf.index(target)
|
until idx = @rbuf.index(terminator)
|
||||||
rbuf_fill
|
rbuf_fill
|
||||||
end
|
end
|
||||||
rbuf_moveto dest, idx + target.size
|
rbuf_moveto dest, idx + terminator.size
|
||||||
rescue EOFError
|
rescue EOFError
|
||||||
raise unless ignore
|
raise unless ignore_eof
|
||||||
rbuf_moveto dest, @rbuf.size
|
rbuf_moveto dest, @rbuf.size
|
||||||
end
|
end
|
||||||
dest
|
dest
|
||||||
end
|
end
|
||||||
|
|
||||||
def readline
|
def readline
|
||||||
ret = readuntil("\n")
|
readuntil("\n").chop
|
||||||
ret.chop!
|
end
|
||||||
ret
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
BLOCK_SIZE = 1024
|
|
||||||
|
|
||||||
def rbuf_fill
|
def rbuf_fill
|
||||||
until IO.select [@socket], nil, nil, @read_timeout
|
until IO.select([@socket], nil, nil, @read_timeout)
|
||||||
on_read_timeout
|
raise TimeoutError, "socket read timeout (#{@read_timeout} sec)"
|
||||||
end
|
end
|
||||||
@rbuf << @socket.sysread(BLOCK_SIZE)
|
@rbuf << @socket.sysread(1024)
|
||||||
end
|
|
||||||
|
|
||||||
def on_read_timeout
|
|
||||||
raise TimeoutError, "socket read timeout (#{@read_timeout} sec)"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def rbuf_moveto( dest, len )
|
def rbuf_moveto( dest, len )
|
||||||
|
@ -194,173 +206,147 @@ module Net
|
||||||
len
|
len
|
||||||
end
|
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
|
### WRITE
|
||||||
###
|
###
|
||||||
|
|
||||||
#
|
|
||||||
# basic write
|
|
||||||
#
|
|
||||||
|
|
||||||
public
|
public
|
||||||
|
|
||||||
def write( str )
|
def write( str )
|
||||||
writing {
|
writing {
|
||||||
do_write str
|
write0 str
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def writeline( str )
|
def writeline( str )
|
||||||
writing {
|
writing {
|
||||||
do_write str + "\r\n"
|
write0 str + "\r\n"
|
||||||
}
|
}
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def writing
|
def writing
|
||||||
@writtensize = 0
|
@written_bytes = 0
|
||||||
@debug_output << '<- ' if @debug_output
|
@debug_output << '<- ' if @debug_output
|
||||||
yield
|
yield
|
||||||
@socket.flush
|
@socket.flush
|
||||||
@debug_output << "\n" if @debug_output
|
@debug_output << "\n" if @debug_output
|
||||||
@writtensize
|
bytes = @written_bytes
|
||||||
|
@written_bytes = nil
|
||||||
|
bytes
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_write( str )
|
def write0( str )
|
||||||
@debug_output << str.dump if @debug_output
|
@debug_output << str.dump if @debug_output
|
||||||
@writtensize += (n = @socket.write(str))
|
len = @socket.write(str)
|
||||||
n
|
@written_bytes += len
|
||||||
|
len
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# message write
|
# Reads string from src calling :each, and write to @socket.
|
||||||
|
# Escapes '.' on the each line head.
|
||||||
#
|
#
|
||||||
|
def write_message_0( src )
|
||||||
public
|
prev = @written_bytes
|
||||||
|
|
||||||
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
|
|
||||||
each_crlf_line(src) do |line|
|
each_crlf_line(src) do |line|
|
||||||
do_write '.' if line[0] == ?.
|
if line[0] == ?.
|
||||||
do_write line
|
then write0 '.' + line
|
||||||
|
else write0 line
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@written_bytes - prev
|
||||||
@writtensize - pre
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# setup @wbuf for each_crlf_line.
|
||||||
|
#
|
||||||
def using_each_crlf_line
|
def using_each_crlf_line
|
||||||
writing {
|
writing {
|
||||||
@wbuf = ''
|
@wbuf = ''
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
if not @wbuf.empty? # unterminated last line
|
if not @wbuf.empty? # unterminated last line
|
||||||
if @wbuf[-1] == ?\r
|
if @wbuf[-1] == ?\r
|
||||||
@wbuf.chop!
|
@wbuf.chop!
|
||||||
end
|
end
|
||||||
@wbuf.concat "\r\n"
|
@wbuf.concat "\r\n"
|
||||||
do_write @wbuf
|
write0 @wbuf
|
||||||
elsif @writtensize == 0 # empty src
|
elsif @written_bytes == 0 # empty src
|
||||||
do_write "\r\n"
|
write0 "\r\n"
|
||||||
end
|
end
|
||||||
do_write ".\r\n"
|
write0 ".\r\n"
|
||||||
|
|
||||||
@wbuf = nil
|
@wbuf = nil
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# extract a CR-LF-terminating-line from @wbuf and yield it.
|
||||||
|
#
|
||||||
def each_crlf_line( src )
|
def each_crlf_line( src )
|
||||||
str = m = beg = nil
|
|
||||||
|
|
||||||
adding(src) do
|
adding(src) do
|
||||||
beg = 0
|
beg = 0
|
||||||
buf = @wbuf
|
buf = @wbuf
|
||||||
while buf.index(/\n|\r\n|\r/, beg)
|
while buf.index(/\n|\r\n|\r/, beg)
|
||||||
m = Regexp.last_match
|
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..."
|
# "...\r" : can follow "\n..."
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
str = buf[ beg ... m.begin(0) ]
|
str = buf[beg ... m.begin(0)]
|
||||||
str.concat "\r\n"
|
str.concat "\r\n"
|
||||||
yield str
|
yield str
|
||||||
beg = m.end(0)
|
beg = m.end(0)
|
||||||
end
|
end
|
||||||
@wbuf = buf[ beg ... buf.size ]
|
@wbuf = buf[beg ... buf.length]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Reads strings from SRC and add to @wbuf, then yield.
|
||||||
|
#
|
||||||
def adding( src )
|
def adding( src )
|
||||||
i = s = nil
|
|
||||||
|
|
||||||
case src
|
case src
|
||||||
when String
|
when String # for speeding up.
|
||||||
0.step(src.size - 1, 2048) do |i|
|
0.step(src.size - 1, 2048) do |i|
|
||||||
@wbuf << src[i,2048]
|
@wbuf << src[i,2048]
|
||||||
yield
|
yield
|
||||||
end
|
end
|
||||||
|
|
||||||
when File
|
when File # for speeding up.
|
||||||
while s = src.read(2048)
|
while s = src.read(2048)
|
||||||
s[0,0] = @wbuf
|
s[0,0] = @wbuf
|
||||||
@wbuf = s
|
@wbuf = s
|
||||||
yield
|
yield
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else # generic reader
|
||||||
src.each do |s|
|
src.each do |s|
|
||||||
@wbuf << s
|
@wbuf << s
|
||||||
yield if @wbuf.size > 2048
|
yield if @wbuf.size > 2048
|
||||||
|
@ -375,18 +361,17 @@ module Net
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def D_off( msg )
|
def LOG_off
|
||||||
D msg
|
@save_debug_out = @debug_output
|
||||||
@savedo, @debug_output = @debug_output, nil
|
@debug_output = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def D_on( msg )
|
def LOG_on
|
||||||
@debug_output = @savedo
|
@debug_output = @save_debug_out
|
||||||
D msg
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def D( msg )
|
def LOG( msg )
|
||||||
@debug_output or return
|
return unless @debug_output
|
||||||
@debug_output << msg
|
@debug_output << msg
|
||||||
@debug_output << "\n"
|
@debug_output << "\n"
|
||||||
end
|
end
|
||||||
|
@ -394,11 +379,14 @@ module Net
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# The writer adapter class
|
||||||
|
#
|
||||||
class WriteAdapter
|
class WriteAdapter
|
||||||
|
|
||||||
def initialize( sock, mid )
|
def initialize( sock, mid )
|
||||||
@socket = sock
|
@socket = sock
|
||||||
@mid = mid
|
@method_id = mid
|
||||||
end
|
end
|
||||||
|
|
||||||
def inspect
|
def inspect
|
||||||
|
@ -406,7 +394,7 @@ module Net
|
||||||
end
|
end
|
||||||
|
|
||||||
def write( str )
|
def write( str )
|
||||||
@socket.__send__ @mid, str
|
@socket.__send__(@method_id, str)
|
||||||
end
|
end
|
||||||
|
|
||||||
alias print write
|
alias print write
|
||||||
|
@ -427,6 +415,9 @@ module Net
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# The reader adapter class for internal use only.
|
||||||
|
#
|
||||||
class ReadAdapter
|
class ReadAdapter
|
||||||
|
|
||||||
def initialize( block )
|
def initialize( block )
|
||||||
|
@ -443,6 +434,11 @@ module Net
|
||||||
|
|
||||||
private
|
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 )
|
def call_block( str )
|
||||||
yield str
|
yield str
|
||||||
end
|
end
|
||||||
|
|
259
lib/net/smtp.rb
259
lib/net/smtp.rb
|
@ -249,7 +249,28 @@ require 'digest/md5'
|
||||||
|
|
||||||
module Net
|
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]
|
Revision = %q$Revision$.split[1]
|
||||||
|
|
||||||
|
@ -259,21 +280,18 @@ module Net
|
||||||
|
|
||||||
def initialize( address, port = nil )
|
def initialize( address, port = nil )
|
||||||
@address = address
|
@address = address
|
||||||
@port = port || SMTP.default_port
|
@port = (port || SMTP.default_port)
|
||||||
|
|
||||||
@esmtp = true
|
@esmtp = true
|
||||||
|
|
||||||
@command = nil
|
|
||||||
@socket = nil
|
@socket = nil
|
||||||
@started = false
|
@started = false
|
||||||
@open_timeout = 30
|
@open_timeout = 30
|
||||||
@read_timeout = 60
|
@read_timeout = 60
|
||||||
|
@error_occured = false
|
||||||
@debug_output = nil
|
@debug_output = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def inspect
|
def inspect
|
||||||
"#<#{self.class} #{address}:#{@port} open=#{@started}>"
|
"#<#{self.class} #{@address}:#{@port} started=#{@started}>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def esmtp?
|
def esmtp?
|
||||||
|
@ -318,7 +336,6 @@ module Net
|
||||||
|
|
||||||
def start( helo = 'localhost.localdomain',
|
def start( helo = 'localhost.localdomain',
|
||||||
user = nil, secret = nil, authtype = nil )
|
user = nil, secret = nil, authtype = nil )
|
||||||
raise IOError, 'SMTP session already started' if @started
|
|
||||||
if block_given?
|
if block_given?
|
||||||
begin
|
begin
|
||||||
do_start(helo, user, secret, authtype)
|
do_start(helo, user, secret, authtype)
|
||||||
|
@ -332,98 +349,98 @@ module Net
|
||||||
end
|
end
|
||||||
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,
|
@socket = InternetMessageIO.open(@address, @port,
|
||||||
@open_timeout, @read_timeout,
|
@open_timeout, @read_timeout,
|
||||||
@debug_output)
|
@debug_output)
|
||||||
@command = SMTPCommand.new(@socket)
|
check_response(critical { recv_response() })
|
||||||
begin
|
begin
|
||||||
if @esmtp
|
if @esmtp
|
||||||
@command.ehlo helo
|
ehlo helodomain
|
||||||
else
|
else
|
||||||
@command.helo helo
|
helo helodomain
|
||||||
end
|
end
|
||||||
rescue ProtocolError
|
rescue ProtocolError
|
||||||
if @esmtp
|
if @esmtp
|
||||||
@esmtp = false
|
@esmtp = false
|
||||||
@command = SMTPCommand.new(@socket)
|
@error_occured = false
|
||||||
retry
|
retry
|
||||||
end
|
end
|
||||||
raise
|
raise
|
||||||
end
|
end
|
||||||
|
authenticate user, secret, authtype if user
|
||||||
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
|
|
||||||
end
|
end
|
||||||
private :do_start
|
private :do_start
|
||||||
|
|
||||||
def finish
|
def finish
|
||||||
raise IOError, 'closing already closed SMTP session' unless @started
|
raise IOError, 'closing already closed SMTP session' unless @started
|
||||||
@command.quit if @command
|
quit if @socket and not @socket.closed? and not @error_occured
|
||||||
@command = nil
|
|
||||||
@socket.close if @socket and not @socket.closed?
|
@socket.close if @socket and not @socket.closed?
|
||||||
@socket = nil
|
@socket = nil
|
||||||
|
@error_occured = false
|
||||||
@started = false
|
@started = false
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# SMTP wrapper
|
# message send
|
||||||
#
|
#
|
||||||
|
|
||||||
def send_mail( mailsrc, from_addr, *to_addrs )
|
public
|
||||||
do_ready from_addr, to_addrs.flatten
|
|
||||||
command().write_mail mailsrc
|
def send_message( msgstr, from_addr, *to_addrs )
|
||||||
|
send0(from_addr, to_addrs.flatten) {
|
||||||
|
@socket.write_message msgstr
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
alias sendmail send_mail # backward compatibility
|
alias send_mail send_message
|
||||||
|
alias sendmail send_message # obsolete
|
||||||
|
|
||||||
def ready( from_addr, *to_addrs, &block )
|
def open_message_stream( from_addr, *to_addrs, &block )
|
||||||
do_ready from_addr, to_addrs.flatten
|
send0(from_addr, to_addrs.flatten) {
|
||||||
command().through_mail(&block)
|
@socket.write_message_by_block(&block)
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
alias ready open_message_stream # obsolete
|
||||||
|
|
||||||
private
|
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?
|
raise ArgumentError, 'mail destination does not given' if to_addrs.empty?
|
||||||
command().mailfrom from_addr
|
|
||||||
command().rcpt to_addrs
|
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
|
||||||
|
|
||||||
def command
|
#
|
||||||
raise IOError, "closed session" unless @command
|
# auth
|
||||||
@command
|
#
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
end
|
def authenticate( user, secret, authtype )
|
||||||
|
__send__("auth_#{authtype || 'cram_md5'}", user, secret)
|
||||||
SMTPSession = SMTP
|
|
||||||
|
|
||||||
|
|
||||||
class SMTPCommand
|
|
||||||
|
|
||||||
def initialize( sock )
|
|
||||||
@socket = sock
|
|
||||||
@in_critical_block = false
|
|
||||||
check_response(critical { recv_response() })
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class} socket=#{@socket.inspect}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def helo( domain )
|
|
||||||
getok('HELO %s', domain)
|
|
||||||
end
|
|
||||||
|
|
||||||
def ehlo( domain )
|
|
||||||
getok('EHLO %s', domain)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def auth_plain( user, secret )
|
def auth_plain( user, secret )
|
||||||
|
@ -434,9 +451,9 @@ module Net
|
||||||
|
|
||||||
def auth_login( user, secret )
|
def auth_login( user, secret )
|
||||||
res = critical {
|
res = critical {
|
||||||
check_response(get_response('AUTH LOGIN'), true)
|
check_response(get_response('AUTH LOGIN'), true)
|
||||||
check_response(get_response(base64_encode(user)), true)
|
check_response(get_response(base64_encode(user)), true)
|
||||||
get_response(base64_encode(secret))
|
get_response(base64_encode(secret))
|
||||||
}
|
}
|
||||||
raise SMTPAuthenticationError, res unless /\A2../ === res
|
raise SMTPAuthenticationError, res unless /\A2../ === res
|
||||||
end
|
end
|
||||||
|
@ -445,20 +462,20 @@ module Net
|
||||||
# CRAM-MD5: [RFC2195]
|
# CRAM-MD5: [RFC2195]
|
||||||
res = nil
|
res = nil
|
||||||
critical {
|
critical {
|
||||||
res = check_response(get_response('AUTH CRAM-MD5'), true)
|
res = check_response(get_response('AUTH CRAM-MD5'), true)
|
||||||
challenge = res.split(/ /)[1].unpack('m')[0]
|
challenge = res.split(/ /)[1].unpack('m')[0]
|
||||||
secret = Digest::MD5.digest(secret) if secret.size > 64
|
secret = Digest::MD5.digest(secret) if secret.size > 64
|
||||||
|
|
||||||
isecret = secret + "\0" * (64 - secret.size)
|
isecret = secret + "\0" * (64 - secret.size)
|
||||||
osecret = isecret.dup
|
osecret = isecret.dup
|
||||||
0.upto(63) do |i|
|
0.upto(63) do |i|
|
||||||
isecret[i] ^= 0x36
|
isecret[i] ^= 0x36
|
||||||
osecret[i] ^= 0x5c
|
osecret[i] ^= 0x5c
|
||||||
end
|
end
|
||||||
tmp = Digest::MD5.digest(isecret + challenge)
|
tmp = Digest::MD5.digest(isecret + challenge)
|
||||||
tmp = Digest::MD5.hexdigest(osecret + tmp)
|
tmp = Digest::MD5.hexdigest(osecret + tmp)
|
||||||
|
|
||||||
res = get_response(base64_encode(user + ' ' + tmp))
|
res = get_response(base64_encode(user + ' ' + tmp))
|
||||||
}
|
}
|
||||||
raise SMTPAuthenticationError, res unless /\A2../ === res
|
raise SMTPAuthenticationError, res unless /\A2../ === res
|
||||||
end
|
end
|
||||||
|
@ -467,45 +484,45 @@ module Net
|
||||||
# expects "str" may not become too long
|
# expects "str" may not become too long
|
||||||
[str].pack('m').gsub(/\s+/, '')
|
[str].pack('m').gsub(/\s+/, '')
|
||||||
end
|
end
|
||||||
private :base64_encode
|
|
||||||
|
#
|
||||||
|
# SMTP command dispatcher
|
||||||
|
#
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def helo( domain )
|
||||||
|
getok('HELO %s', domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ehlo( domain )
|
||||||
|
getok('EHLO %s', domain)
|
||||||
|
end
|
||||||
|
|
||||||
def mailfrom( fromaddr )
|
def mailfrom( fromaddr )
|
||||||
getok('MAIL FROM:<%s>', fromaddr)
|
getok('MAIL FROM:<%s>', fromaddr)
|
||||||
end
|
end
|
||||||
|
|
||||||
def rcpt( toaddrs )
|
def rcptto( to )
|
||||||
toaddrs.each do |i|
|
getok('RCPT TO:<%s>', to)
|
||||||
getok('RCPT TO:<%s>', i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_mail( src )
|
|
||||||
res = critical {
|
|
||||||
check_response(get_response('DATA'), true)
|
|
||||||
@socket.write_message src
|
|
||||||
recv_response()
|
|
||||||
}
|
|
||||||
check_response(res)
|
|
||||||
end
|
|
||||||
|
|
||||||
def through_mail( &block )
|
|
||||||
res = critical {
|
|
||||||
check_response(get_response('DATA'), true)
|
|
||||||
@socket.through_message(&block)
|
|
||||||
recv_response()
|
|
||||||
}
|
|
||||||
check_response(res)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def quit
|
def quit
|
||||||
getok('QUIT')
|
getok('QUIT')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# row level library
|
||||||
|
#
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def getok( fmt, *args )
|
def getok( fmt, *args )
|
||||||
@socket.writeline sprintf(fmt, *args)
|
res = critical {
|
||||||
check_response(critical { recv_response() })
|
@socket.writeline sprintf(fmt, *args)
|
||||||
|
recv_response()
|
||||||
|
}
|
||||||
|
return check_response(res)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_response( fmt, *args )
|
def get_response( fmt, *args )
|
||||||
|
@ -524,29 +541,29 @@ module Net
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_response( res, allow_continue = false )
|
def check_response( res, allow_continue = false )
|
||||||
etype = case res[0]
|
return res if /\A2/ === res
|
||||||
when ?2 then nil
|
return res if allow_continue and /\A354/ === res
|
||||||
when ?3 then allow_continue ? nil : ProtoUnknownError
|
err = case res
|
||||||
when ?4 then ProtoServerError
|
when /\A4/ then SMTPServerBusy
|
||||||
when ?5 then
|
when /\A50/ then SMTPSyntaxError
|
||||||
case res[1]
|
when /\A55/ then SMTPFatalError
|
||||||
when ?0 then ProtoSyntaxError
|
else SMTPUnknownError
|
||||||
when ?3 then ProtoAuthError
|
end
|
||||||
when ?5 then ProtoFatalError
|
raise err, res
|
||||||
end
|
|
||||||
end
|
|
||||||
raise etype, res if etype
|
|
||||||
res
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def critical
|
def critical( &block )
|
||||||
return if @in_critical_block
|
return '200 dummy reply code' if @error_occured
|
||||||
@in_critical_block = true
|
begin
|
||||||
result = yield()
|
return yield()
|
||||||
@in_critical_block = false
|
rescue Exception
|
||||||
result
|
@error_occured = true
|
||||||
|
raise
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end # class SMTP
|
||||||
|
|
||||||
|
SMTPSession = SMTP
|
||||||
|
|
||||||
end # module Net
|
end # module Net
|
||||||
|
|
Loading…
Add table
Reference in a new issue