1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

* lib/net/protocol.rb: remove Protocol class.

* lib/net/smtp.rb (SMTP): ditto.
* lib/net/pop.rb (POP3): ditto.
* lib/net/http.rb (HTTP): ditto.
* lib/net/protocol.rb: remove Command class.
* lib/net/smtp.rb (SMTPCommand): ditto.
* lib/net/pop.rb (POP3Command): ditto.
* lib/net/pop.rb: remove APOPCommand class.
* lib/net/protocol.rb: remove Code class and its all subclasses.
* lib/net/protocol.rb: remove Response class and its all subclasses.
* lib/net/pop.rb (POPMail): new method unique_id (alias uidl).


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@3747 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
aamine 2003-05-02 14:35:01 +00:00
parent fd5f913f33
commit e3056c8803
5 changed files with 656 additions and 643 deletions

View file

@ -1,3 +1,28 @@
Fri May 2 23:29:53 2003 Minero Aoki <aamine@loveruby.net>
* lib/net/protocol.rb: remove Protocol class.
* lib/net/smtp.rb (SMTP): ditto.
* lib/net/pop.rb (POP3): ditto.
* lib/net/http.rb (HTTP): ditto.
* lib/net/protocol.rb: remove Command class.
* lib/net/smtp.rb (SMTPCommand): ditto.
* lib/net/pop.rb (POP3Command): ditto.
* lib/net/pop.rb: remove APOPCommand class.
* lib/net/protocol.rb: remove Code class and its all subclasses.
* lib/net/protocol.rb: remove Response class and its all
subclasses.
* lib/net/pop.rb (POPMail): new method unique_id (alias uidl).
Fri May 2 18:17:37 2003 Yukihiro Matsumoto <matz@ruby-lang.org>
* compar.c (cmp_gt): raises ArgumentError when "<=>" give nil.

View file

@ -562,11 +562,12 @@ module Net
class HTTPHeaderSyntaxError < StandardError; end
class HTTP < Protocol
class HTTP
Revision = %q$Revision$.split[1]
HTTPVersion = '1.1'
#
# for backward compatibility
#
@ -591,7 +592,6 @@ module Net
end
private_class_method :setimplversion
#
# short cut methods
#
@ -644,13 +644,17 @@ module Net
end
private_class_method :get_by_uri
#
# connection
# HTTP session management
#
protocol_param :default_port, '80'
protocol_param :socket_type, '::Net::InternetMessageIO'
def HTTP.default_port
80
end
def HTTP.socket_type
InternetMessageIO
end
class << HTTP
def start( address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil, &block )
@ -666,25 +670,94 @@ module Net
end
end
def initialize( addr, port = nil )
super
def initialize( address, port = nil )
@address = address
@port = port || HTTP.default_port
@curr_http_version = HTTPVersion
@seems_1_0_server = false
@close_on_empty_response = false
@socket = nil
@started = false
@open_timeout = 30
@read_timeout = 60
@debug_output = nil
end
def inspect
"#<#{self.class} #{@address}:#{@port} open=#{active?}>"
end
def set_debug_output( arg ) # :nodoc:
@debug_output = arg
end
attr_reader :address
attr_reader :port
attr_accessor :open_timeout
attr_reader :read_timeout
def read_timeout=( sec )
@socket.read_timeout = sec if @socket
@read_timeout = sec
end
def started?
@started
end
alias active? started?
attr_accessor :close_on_empty_response
private
def start
raise IOError, 'HTTP session already opened' if @started
if block_given?
begin
do_start
return yield(self)
ensure
finish
end
end
do_start
self
end
def do_start
conn_socket
@socket = self.class.socket_type.open(conn_address(), conn_port(),
@open_timeout, @read_timeout,
@debug_output)
on_connect
@started = true
end
private :do_start
def do_finish
disconn_socket
def conn_address
address()
end
private :conn_address
def conn_port
port()
end
private :conn_port
def on_connect
end
private :on_connect
def finish
raise IOError, 'closing already closed HTTP session' unless @started
@socket.close if @socket and not @socket.closed?
@socket = nil
@started = false
nil
end
#
# proxy
@ -784,9 +857,8 @@ module Net
end
end
#
# http operations
# HTTP operations
#
public
@ -888,7 +960,8 @@ module Net
def begin_transport( req )
if @socket.closed?
reconn_socket
@socket.reopen @open_timeout
on_connect
end
if @seems_1_0_server
req['connection'] = 'close'
@ -930,7 +1003,6 @@ module Net
false
end
#
# utils
#
@ -954,7 +1026,7 @@ module Net
###
### header
### Header
###
module HTTPHeader
@ -1012,6 +1084,7 @@ module Net
def canonical( k )
k.split(/-/).map {|i| i.capitalize }.join('-')
end
private :canonical
def range
s = @header['range'] or return nil
@ -1101,7 +1174,7 @@ module Net
###
### request
### Request
###
class HTTPGenericRequest
@ -1188,14 +1261,12 @@ module Net
class HTTPRequest < HTTPGenericRequest
def initialize( path, initheader = nil )
super self.class::METHOD,
self.class::REQUEST_HAS_BODY,
self.class::RESPONSE_HAS_BODY,
path, initheader
end
end
@ -1228,10 +1299,32 @@ module Net
end
###
### Response
###
module HTTPExceptions
def initialize( msg, res )
super msg
@response = res
end
attr_reader :response
alias data response
end
class HTTPError < ProtocolError
include HTTPExceptions
end
class HTTPRetriableError < ProtoRetriableError
include HTTPExceptions
end
# We cannot use the name "HTTPServerError", it is the name of the response.
class HTTPServerException < ProtoServerError
include HTTPExceptions
end
class HTTPFatalError < ProtoFatalError
include HTTPExceptions
end
###
### response
###
class HTTPResponse
# predefine HTTPResponse class to allow inheritance
@ -1245,30 +1338,29 @@ module Net
end
end
class HTTPUnknownResponse < HTTPResponse
HAS_BODY = true
EXCEPTION_TYPE = ProtocolError
EXCEPTION_TYPE = HTTPError
end
class HTTPInformation < HTTPResponse # 1xx
HAS_BODY = false
EXCEPTION_TYPE = ProtocolError
EXCEPTION_TYPE = HTTPError
end
class HTTPSuccess < HTTPResponse # 2xx
HAS_BODY = true
EXCEPTION_TYPE = ProtocolError
EXCEPTION_TYPE = HTTPError
end
class HTTPRedirection < HTTPResponse # 3xx
HAS_BODY = true
EXCEPTION_TYPE = ProtoRetriableError
EXCEPTION_TYPE = HTTPRetriableError
end
class HTTPClientError < HTTPResponse # 4xx
HAS_BODY = true
EXCEPTION_TYPE = ProtoServerError # for backward compatibility
EXCEPTION_TYPE = HTTPServerException # for backward compatibility
end
class HTTPServerError < HTTPResponse # 5xx
HAS_BODY = true
EXCEPTION_TYPE = ProtoFatalError # for backward compatibility
EXCEPTION_TYPE = HTTPFatalError # for backward compatibility
end
class HTTPContinue < HTTPInformation # 100
@ -1649,12 +1741,6 @@ module Net
# for backward compatibility
module NetPrivate
HTTPResponse = ::Net::HTTPResponse
HTTPGenericRequest = ::Net::HTTPGenericRequest
HTTPRequest = ::Net::HTTPRequest
HTTPHeader = ::Net::HTTPHeader
end
HTTPInformationCode = HTTPInformation
HTTPSuccessCode = HTTPSuccess
HTTPRedirectionCode = HTTPRedirection

View file

@ -54,6 +54,7 @@ Replace 'pop3.server.address' your POP3 server address.
(3) close POP session by calling POP3#finish or use block form #start.
This example is using block form #start to close the session.
=== Enshort Code
The example above is very verbose. You can enshort code by using
@ -132,26 +133,48 @@ You can use utility method, Net::POP3.APOP(). Example:
require 'net/pop'
# use APOP authentication if $isapop == true
# Use APOP authentication if $isapop == true
pop = Net::POP3.APOP($isapop).new('apop.server.address', 110)
pop.start(YourAccount', 'YourPassword') {|pop|
# Rest code is same.
}
=== Fetch Only Selected Mail Using POP UIDL Function
== Net::POP3 class
If your POP server provides UIDL function,
you can pop only selected mails from POP server.
e.g.
def need_pop?( id )
# determine if we need pop this mail...
end
Net::POP3.start('pop.server', 110,
'Your account', 'Your password') {|pop|
pop.mails.select {|m| need_pop?(m.unique_id) }.each do |m|
do_something(m.pop)
end
}
POPMail#unique_id method returns the unique-id of the message (String).
Normally unique-id is a hash of the message.
== class Net::POP3
=== Class Methods
: new( address, port = 110, apop = false )
: new( address, port = 110, isapop = false )
creates a new Net::POP3 object.
This method does not open TCP connection yet.
This method does NOT open TCP connection yet.
: start( address, port = 110, account, password )
: start( address, port = 110, account, password ) {|pop| .... }
equals to Net::POP3.new( address, port ).start( account, password )
: start( address, port = 110, account, password, isapop = false )
: start( address, port = 110, account, password, isapop = false ) {|pop| .... }
equals to Net::POP3.new(address, port, isapop).start(account, password).
This method raises POPAuthenticationError if authentication is failed.
Net::POP3.start( addr, port, account, password ) {|pop|
# Typical usage
Net::POP3.start(addr, port, account, password) {|pop|
pop.each_mail do |m|
file.write m.pop
m.delete
@ -163,17 +186,17 @@ You can use utility method, Net::POP3.APOP(). Example:
returns Net::POP3 class object if false.
Use this method like:
# example 1
# Example 1
pop = Net::POP3::APOP($isapop).new( addr, port )
# example 2
# Example 2
Net::POP3::APOP($isapop).start( addr, port ) {|pop|
....
}
: foreach( address, port = 110, account, password ) {|mail| .... }
: foreach( address, port = 110, account, password, isapop = false ) {|mail| .... }
starts POP3 protocol and iterates for each POPMail object.
This method equals to
This method equals to:
Net::POP3.start( address, port, account, password ) {|pop|
pop.each_mail do |m|
@ -181,29 +204,33 @@ You can use utility method, Net::POP3.APOP(). Example:
end
}
# example
This method raises POPAuthenticationError if authentication is failed.
# Typical usage
Net::POP3.foreach( 'your.pop.server', 110,
'YourAccount', 'YourPassword' ) do |m|
file.write m.pop
m.delete if $DELETE
end
: delete_all( address, port = 110, account, password )
: delete_all( address, port = 110, account, password ) {|mail| .... }
: delete_all( address, port = 110, account, password, isapop = false )
: delete_all( address, port = 110, account, password, isapop = false ) {|mail| .... }
starts POP3 session and delete all mails.
If block is given, iterates for each POPMail object before delete.
This method raises POPAuthenticationError if authentication is failed.
# example
# Example
Net::POP3.delete_all( addr, nil, 'YourAccount', 'YourPassword' ) do |m|
m.pop file
end
: auth_only( address, port = 110, account, password )
: auth_only( address, port = 110, account, password, isapop = false )
(just for POP-before-SMTP)
opens POP3 session and does autholize and quit.
This method must not be called while POP3 session is opened.
This method raises POPAuthenticationError if authentication is failed.
# example
# Example
Net::POP3.auth_only( 'your.pop3.server',
nil, # using default (110)
'YourAccount',
@ -218,7 +245,9 @@ You can use utility method, Net::POP3.APOP(). Example:
When called with block, gives a POP3 object to block and
closes the session after block call finish.
: active?
This method raises POPAuthenticationError if authentication is failed.
: started?
true if POP3 session is started.
: address
@ -245,44 +274,62 @@ You can use utility method, Net::POP3.APOP(). Example:
: mails
an array of Net::POPMail objects.
This array is renewed when session started.
This array is renewed when session restarts.
This method raises POPError if any problem happend.
: each_mail {|popmail| .... }
: each {|popmail| .... }
is equals to "pop3.mails.each"
This method raises POPError if any problem happend.
: delete_all
: delete_all {|popmail| .... }
deletes all mails on server.
If called with block, gives mails to the block before deleting.
# example
# Example
n = 1
pop.delete_all do |m|
File.open("inbox/#{n}") {|f| f.write m.pop }
n += 1
end
This method raises POPError if any problem happend.
: auth_only( account, password )
(just for POP-before-SMTP)
opens POP3 session and does autholize and quit.
This method must not be called while POP3 session is opened.
# example
pop = Net::POP3.new( 'your.pop3.server' )
pop.auth_only 'YourAccount', 'YourPassword'
opens POP3 session, does authorization, then quit.
You must not call this method after POP3 session is opened.
This method raises POPAuthenticationError if authentication is failed.
# Typical usage
pop = Net::POP3.new('your.pop3.server')
pop.auth_only('Your account', 'Your password')
Net::SMTP.start(....) {|smtp|
....
}
: reset
reset the session. All "deleted mark" are removed.
== Net::APOP
This method raises POPError if any problem happend.
== class Net::APOP
This class defines no new methods.
Only difference from POP3 is using APOP authentification.
=== Super Class
Net::POP3
== Net::POPMail
== class Net::POPMail
A class of mail which exists on POP server.
@ -291,7 +338,9 @@ A class of mail which exists on POP server.
: pop( dest = '' )
This method fetches a mail and write to 'dest' using '<<' method.
# example
This method raises POPError if any problem happend.
# Typical usage
allmails = nil
POP3.start( 'your.pop3.server', 110,
'YourAccount, 'YourPassword' ) {|pop|
@ -301,7 +350,9 @@ A class of mail which exists on POP server.
: pop {|str| .... }
gives the block part strings of a mail.
# example
This method raises POPError if any problem happend.
# Typical usage
POP3.start( 'localhost', 110 ) {|pop3|
pop3.each_mail do |m|
m.pop do |str|
@ -311,20 +362,32 @@ A class of mail which exists on POP server.
}
: header
This method fetches only mail header.
fetches only mail header.
This method raises POPError if any problem happend.
: top( lines )
This method fetches mail header and LINES lines of body.
fetches mail header and LINES lines of body.
This method raises POPError if any problem happend.
: delete
deletes mail on server.
This method raises POPError if any problem happend.
: size
mail size (bytes)
: deleted?
true if mail was deleted
: unique_id
returns an unique-id of the message.
Normally unique-id is a hash of the message.
This method raises POPError if any problem happend.
=end
require 'net/protocol'
@ -333,38 +396,55 @@ require 'digest/md5'
module Net
class BadResponseError < StandardError; end
class POPError < ProtocolError; end
class POPAuthenticationError < ProtoAuthError; end
class POPBadResponse < StandardError; end
class POP3 < Protocol
class POP3
protocol_param :default_port, '110'
protocol_param :command_type, '::Net::POP3Command'
protocol_param :apop_command_type, '::Net::APOPCommand'
protocol_param :mail_type, '::Net::POPMail'
protocol_param :socket_type, '::Net::InternetMessageIO'
Revision = %q$Revision$.split[1]
#
# Class Parameters
#
def POP3.default_port
110
end
def POP3.socket_type
Net::InternetMessageIO
end
#
# Utilities
#
def POP3.APOP( isapop )
isapop ? APOP : POP3
end
def POP3.foreach( address, port = nil,
account = nil, password = nil, &block )
start(address, port, account, password) {|pop|
account = nil, password = nil,
isapop = false, &block )
start(address, port, account, password, isapop) {|pop|
pop.each_mail(&block)
}
end
def POP3.delete_all( address, port = nil,
account = nil, password = nil, &block )
start(address, port, account, password) {|pop|
account = nil, password = nil,
isapop = false, &block )
start(address, port, account, password, isapop) {|pop|
pop.delete_all(&block)
}
end
def POP3.auth_only( address, port = nil,
account = nil, password = nil )
new(address, port).auth_only account, password
account = nil, password = nil,
isapop = false )
new(address, port, isapop).auth_only account, password
end
def auth_only( account, password )
@ -375,39 +455,115 @@ module Net
end
#
# connection
# Session management
#
def initialize( addr, port = nil, apop = false )
super addr, port
@mails = nil
@apop = false
def POP3.start( address, port = nil,
account = nil, password = nil,
isapop = false, &block )
new(address, port, isapop).start(account, password, &block)
end
private
def initialize( addr, port = nil, isapop = false )
@address = addr
@port = port || self.class.default_port
@apop = isapop
@command = nil
@socket = nil
@started = false
@open_timeout = 30
@read_timeout = 60
@debug_output = nil
@mails = nil
@nmails = nil
@bytes = nil
end
def apop?
@apop
end
def inspect
"#<#{self.class} #{@address}:#{@port} open=#{@started}>"
end
def set_debug_output( arg ) # :nodoc:
@debug_output = arg
end
attr_reader :address
attr_reader :port
attr_accessor :open_timeout
attr_reader :read_timeout
def read_timeout=( sec )
@command.socket.read_timeout = sec if @command
@read_timeout = sec
end
def started?
@started
end
alias active? started? # backward compatibility
def start( account, password )
raise IOError, 'already closed POP session' if @started
if block_given?
begin
do_start account, password
return yield(self)
ensure
finish unless @started
end
else
do_start acount, password
return self
end
end
def do_start( account, password )
conn_socket
conn_command
@command.auth account, password
@socket = self.class.socket_type.open(@address, @port,
@open_timeout, @read_timeout, @debug_output)
on_connect
@command = POP3Command.new(@socket)
if apop?
@command.apop account, password
else
@command.auth account, password
end
@started = true
end
private :do_start
def conn_command
@command = (@apop ? self.class.apop_command_type :
self.class.command_type ).new(socket())
def on_connect
end
private :on_connect
def do_finish
def finish
raise IOError, 'already closed POP session' unless @started
@mails = nil
disconn_command
disconn_socket
@command.quit if @command
@command = nil
@socket.close if @socket and not @socket.closed?
@socket = nil
@started = false
end
#
# POP operations
#
def command
raise IOError, 'POP session not opened yet' \
if not @socket or @socket.closed?
@command
end
private :command
public
#
# POP protocol wrapper
#
def mail_size
return @nmails if @nmails
@ -422,21 +578,17 @@ module Net
end
def mails
return @mails if @mails
return @mails.dup if @mails
if mail_size() == 0
# some popd raises error for LIST on the empty mailbox.
@mails = []
return @mails
return []
end
mails = []
mailclass = self.class.mail_type
command().list.each_with_index do |size,idx|
mails.push mailclass.new(idx, size, command()) if size
end
@mails = mails.freeze
@mails
@mails = command().list.map {|num, size|
POPMail.new(num, size, self, command())
}
@mails.dup
end
def each_mail( &block )
@ -461,25 +613,23 @@ module Net
end
end
def command
io_check
super
end
def io_check
raise IOError, 'POP session is not opened yet'\
if not socket() or socket().closed?
# internal use only (called from POPMail#uidl).
def set_all_uids
command().uidl.each do |num, uid|
@mails.find {|m| m.number == num }.uid = uid
end
end
end
# aliases
POP = POP3
POPSession = POP3
POP3Session = POP3
class APOP < POP3
def APOP.command_type
APOPCommand
def apop?
true
end
end
@ -488,171 +638,188 @@ module Net
class POPMail
def initialize( n, s, cmd )
@num = n
@size = s
def initialize( num, size, pop, cmd )
@number = num
@size = size
@pop = pop
@command = cmd
@deleted = false
@uid = nil
end
attr_reader :number
attr_reader :size
def inspect
"#<#{self.class} #{@num}#{@deleted ? ' deleted' : ''}>"
"#<#{self.class} #{@number}#{@deleted ? ' deleted' : ''}>"
end
def pop( dest = '', &block )
dest = ReadAdapter.new(block) if block
@command.retr @num, dest
@command.retr(@number, (block ? ReadAdapter.new(block) : dest))
end
alias all pop
alias mail pop
alias all pop # backward compatibility
alias mail pop # backward compatibility
def top( lines, dest = '' )
@command.top @num, lines, dest
@command.top(@number, lines, dest)
end
def header( dest = '' )
top 0, dest
top(0, dest)
end
def delete
@command.dele @num
@command.dele @number
@deleted = true
end
alias delete! delete
alias delete! delete # backward compatibility
def deleted?
@deleted
end
def uidl
@command.uidl @num
def unique_id
return @uid if @uid
@pop.set_all_uids
@uid
end
alias uidl unique_id
# internal use only (used from POP3#set_all_uids).
def uid=( uid )
@uid = uid
end
end
class POP3Command < Command
class POP3Command
def initialize( sock )
super
atomic {
check_reply SuccessCode
}
@socket = sock
@in_critical_block = false
res = check_response(critical { recv_response() })
@apop_stamp = res.slice(/<.+>/)
end
def auth( account, pass )
atomic {
@socket.writeline 'USER ' + account
check_reply_auth
def inspect
"#<#{self.class} socket=#{@socket}>"
end
@socket.writeline 'PASS ' + pass
check_reply_auth
}
def auth( account, password )
check_response_auth(critical { get_response('USER ' + account) })
check_response_auth(critical { get_response('PASS ' + password) })
end
def apop( account, password )
raise POPAuthenticationError.new('not APOP server; cannot login', nil)\
unless @apop_stamp
check_response_auth(critical {
get_reply('APOP %s %s',
account,
Digest::MD5.hexdigest(@apop_stamp + password))
})
end
def list
atomic {
critical {
getok 'LIST'
list = []
@socket.each_list_item do |line|
m = /\A(\d+)[ \t]+(\d+)/.match(line) or
raise BadResponse, "bad response: #{line}"
list[m[1].to_i] = m[2].to_i
raise POPBadResponse, "bad response: #{line}"
list.push [m[1].to_i, m[2].to_i]
end
return list
list
}
end
def stat
atomic {
@socket.writeline 'STAT'
line = @socket.readline
m = /\A\+OK (\d+)[ \t]+(\d+)/.match(line) or
raise BadResponseError, "illegal response: #{line}"
return [m[1].to_i, m[2].to_i]
}
res = check_response(critical { get_response('STAT') })
m = /\A\+OK\s+(\d+)\s+(\d+)/.match(res) or
raise POPBadResponse, "wrong response format: #{res}"
[m[1].to_i, m[2].to_i]
end
def rset
atomic {
getok 'RSET'
}
check_reply(critical { get_response 'RSET' })
end
def top( num, lines = 0, dest = '' )
atomic {
getok sprintf('TOP %d %d', num, lines)
@socket.read_message_to dest
critical {
getok('TOP %d %d', num, lines)
@socket.read_message_to(dest)
}
end
def retr( num, dest = '' )
atomic {
getok sprintf('RETR %d', num)
critical {
getok('RETR %d', num)
@socket.read_message_to dest
}
end
def dele( num )
atomic {
getok sprintf('DELE %d', num)
}
check_response(critical { get_response('DELE %d', num) })
end
def uidl( num )
atomic {
getok(sprintf('UIDL %d', num)).message.split(/ /)[1]
}
def uidl( num = nil )
if num
res = check_response(critical { get_response('UIDL %d', num) })
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
}
end
end
def quit
atomic {
getok 'QUIT'
}
check_response(critical { get_response('QUIT') })
end
private
def check_reply_auth
begin
return check_reply(SuccessCode)
rescue ProtocolError => err
raise ProtoAuthError.new('Fail to POP authentication', err.response)
end
def getok( *reqs )
@socket.writeline sprintf(*reqs)
check_response(recv_response())
end
def get_reply
str = @socket.readline
if /\A\+/ === str
Response.new(SuccessCode, str[0,3], str[3, str.size - 3].strip)
else
Response.new(ErrorCode, str[0,4], str[4, str.size - 4].strip)
end
def get_response( *reqs )
@socket.writeline sprintf(*reqs)
recv_response()
end
end
class APOPCommand < POP3Command
def initialize( sock )
@stamp = super(sock).message.slice(/<.+>/) or
raise ProtoAuthError.new("not APOP server: cannot login", nil)
def recv_response
@socket.readline
end
def auth( account, pass )
atomic {
@socket.writeline sprintf('APOP %s %s',
account,
Digest::MD5.hexdigest(@stamp + pass))
check_reply_auth
}
def check_response( res )
raise POPError, res unless /\A\+OK/i === res
res
end
def check_response_auth( res )
raise POPAuthenticationError, res unless /\A\+OK/i === res
res
end
def critical
return if @in_critical_block
# Do not use ensure-block.
@in_critical_block = true
result = yield
@in_critical_block = false
result
end
end

View file

@ -2,7 +2,8 @@
= net/protocol.rb
Copyright (c) 1999-2002 Yukihiro Matsumoto
Copyright (c) 1999-2003 Yukihiro Matsumoto
Copyright (c) 1999-2003 Minero Aoki
written & maintained by Minero Aoki <aamine@loveruby.net>
@ -23,211 +24,6 @@ require 'timeout'
module Net
class Protocol
Version = '1.2.3'
Revision = '$Revision$'.slice(/[\d\.]+/)
class << self
def port
default_port
end
private
def protocol_param( name, val )
module_eval <<-EOS, __FILE__, __LINE__ + 1
def self.#{name.id2name}
#{val}
end
EOS
end
end
#
# --- Configuration Staffs for Sub Classes ---
#
# class method default_port
# class method command_type
# class method socket_type
#
# private method do_start
# private method do_finish
#
# private method conn_address
# private method conn_port
#
def Protocol.start( address, port = nil, *args )
instance = new(address, port)
if block_given?
instance.start(*args) {
return yield(instance)
}
else
instance.start(*args)
instance
end
end
def initialize( addr, port = nil )
@address = addr
@port = port || self.class.default_port
@command = nil
@socket = nil
@started = false
@open_timeout = 30
@read_timeout = 60
@debug_output = nil
end
attr_reader :address
attr_reader :port
attr_reader :command
attr_reader :socket
attr_accessor :open_timeout
attr_reader :read_timeout
def read_timeout=( sec )
@socket.read_timeout = sec if @socket
@read_timeout = sec
end
def started?
@started
end
alias active? started?
def set_debug_output( arg ) # un-documented
@debug_output = arg
end
def inspect
"#<#{self.class} #{@address}:#{@port} open=#{active?}>"
end
#
# open
#
def start( *args )
@started and raise IOError, 'protocol has been opened already'
if block_given?
begin
do_start(*args)
@started = true
return yield(self)
ensure
finish if @started
end
end
do_start(*args)
@started = true
self
end
private
# abstract do_start()
def conn_socket
@socket = self.class.socket_type.open(
conn_address(), conn_port(),
@open_timeout, @read_timeout, @debug_output )
on_connect
end
alias conn_address address
alias conn_port port
def reconn_socket
@socket.reopen @open_timeout
on_connect
end
def conn_command
@command = self.class.command_type.new(@socket)
end
def on_connect
end
#
# close
#
public
def finish
raise IOError, 'closing already closed protocol' unless @started
do_finish
@started = false
nil
end
private
# abstract do_finish()
def disconn_command
@command.quit if @command and not @command.critical?
@command = nil
end
def disconn_socket
@socket.close if @socket and not @socket.closed?
@socket = nil
end
end
Session = Protocol
class Response
def initialize( ctype, code, msg )
@code_type = ctype
@code = code
@message = msg
super()
end
attr_reader :code_type
attr_reader :code
attr_reader :message
alias msg message
def inspect
"#<#{self.class} #{@code}>"
end
def error!
raise error_type().new(code + ' ' + @message.dump, self)
end
def error_type
@code_type.error_type
end
end
class ProtocolError < StandardError; end
class ProtoSyntaxError < ProtocolError; end
class ProtoFatalError < ProtocolError; end
@ -238,129 +34,6 @@ module Net
class ProtoRetriableError < ProtocolError; end
ProtocRetryError = ProtoRetriableError
class ProtocolError
def initialize( msg, resp )
super msg
@response = resp
end
attr_reader :response
alias data response
def inspect
"#<#{self.class} #{self.message}>"
end
end
class Code
def initialize( paren, err )
@parents = [self] + paren
@error_type = err
end
def parents
@parents.dup
end
attr_reader :error_type
def inspect
"#<#{self.class} #{sprintf '0x%x', __id__}>"
end
def ===( response )
response.code_type.parents.each do |c|
return true if c == self
end
false
end
def mkchild( err = nil )
self.class.new(@parents, err || @error_type)
end
end
ReplyCode = Code.new([], ProtoUnknownError)
InformationCode = ReplyCode.mkchild(ProtoUnknownError)
SuccessCode = ReplyCode.mkchild(ProtoUnknownError)
ContinueCode = ReplyCode.mkchild(ProtoUnknownError)
ErrorCode = ReplyCode.mkchild(ProtocolError)
SyntaxErrorCode = ErrorCode.mkchild(ProtoSyntaxError)
FatalErrorCode = ErrorCode.mkchild(ProtoFatalError)
ServerErrorCode = ErrorCode.mkchild(ProtoServerError)
AuthErrorCode = ErrorCode.mkchild(ProtoAuthError)
RetriableCode = ReplyCode.mkchild(ProtoRetriableError)
UnknownCode = ReplyCode.mkchild(ProtoUnknownError)
class Command
def initialize( sock )
@socket = sock
@last_reply = nil
@atomic = false
end
attr_accessor :socket
attr_reader :last_reply
def inspect
"#<#{self.class} socket=#{@socket.inspect} critical=#{@atomic}>"
end
# abstract quit()
private
def check_reply( *oks )
@last_reply = get_reply()
reply_must @last_reply, *oks
end
# abstract get_reply()
def reply_must( rep, *oks )
oks.each do |i|
return rep if i === rep
end
rep.error!
end
def getok( line, expect = SuccessCode )
@socket.writeline line
check_reply expect
end
#
# critical session
#
public
def critical?
@atomic
end
def error_ok
@atomic = false
end
private
def atomic
@atomic = true
ret = yield
@atomic = false
ret
end
end
class InternetMessageIO
@ -765,16 +438,4 @@ module Net
end
# for backward compatibility
module NetPrivate
Response = ::Net::Response
Command = ::Net::Command
Socket = ::Net::InternetMessageIO
BufferedSocket = ::Net::InternetMessageIO
WriteAdapter = ::Net::WriteAdapter
ReadAdapter = ::Net::ReadAdapter
end
BufferedSocket = ::Net::InternetMessageIO
end # module Net

View file

@ -44,7 +44,7 @@ executed.
require 'net/smtp'
Net::SMTP.start('your.smtp.server', 25) {|smtp|
# use smtp object only in this block
# use SMTP object only in this block
}
Replace 'your.smtp.server' by your SMTP server. Normally
@ -144,9 +144,15 @@ the SMTP session by inspecting HELO domain.
authentication by using AUTH command. :plain or :cram_md5 is
allowed for AUTHTYPE.
: active?
: started?
true if SMTP session is started.
: esmtp?
true if the SMTP object uses ESMTP.
: esmtp=(b)
set wheather SMTP should use ESMTP.
: address
the address to connect
@ -209,14 +215,15 @@ the SMTP session by inspecting HELO domain.
== Exceptions
SMTP objects raise these exceptions:
: Net::ProtoSyntaxError
syntax error (errno.500)
Syntax error (errno.500)
: Net::ProtoFatalError
fatal error (errno.550)
Fatal error (errno.550)
: Net::ProtoUnknownError
unknown error. (is probably bug)
Unknown error. (is probably bug)
: Net::ProtoServerBusy
temporary error (errno.420/450)
Temporal error (errno.420/450)
=end
@ -226,16 +233,31 @@ require 'digest/md5'
module Net
class SMTP < Protocol
class SMTP
protocol_param :default_port, '25'
protocol_param :command_type, '::Net::SMTPCommand'
protocol_param :socket_type, '::Net::InternetMessageIO'
Revision = %q$Revision$.split[1]
def SMTP.default_port
25
end
def initialize( address, port = nil )
@address = address
@port = port || SMTP.default_port
def initialize( addr, port = nil )
super
@esmtp = true
@command = nil
@socket = nil
@started = false
@open_timeout = 30
@read_timeout = 60
@debug_output = nil
end
def inspect
"#<#{self.class} #{address}:#{@port} open=#{@started}>"
end
def esmtp?
@ -248,27 +270,70 @@ module Net
alias esmtp esmtp?
private
attr_reader :address
attr_reader :port
def do_start( helo = 'localhost.localdomain',
user = nil, secret = nil, authtype = nil )
conn_socket
conn_command
attr_accessor :open_timeout
attr_reader :read_timeout
def read_timeout=( sec )
@socket.read_timeout = sec if @socket
@read_timeout = sec
end
def set_debug_output( arg )
@debug_output = arg
end
#
# SMTP session control
#
def SMTP.start( address, port = nil,
helo = 'localhost.localdomain',
user = nil, secret = nil, authtype = nil,
&block)
new(address, port).start(helo, user, secret, authtype, &block)
end
def started?
@started
end
def start( helo = 'localhost.localdomain',
user = nil, secret = nil, authtype = nil )
raise IOError, 'SMTP session opened already' if @started
if block_given?
begin
do_start(helo, user, secret, authtype)
return yield(self)
ensure
finish if @started
end
else
do_start(helo, user, secret, authtype)
return self
end
end
def do_start( helo, user, secret, authtype )
@socket = InternetMessageIO.open(@address, @port,
@open_timeout, @read_timeout,
@debug_output)
@command = SMTPCommand.new(@socket)
begin
if @esmtp
command().ehlo helo
@command.ehlo helo
else
command().helo helo
@command.helo helo
end
rescue ProtocolError
if @esmtp
@esmtp = false
command().error_ok
@command.error_ok
retry
else
raise
end
raise
end
if user or secret
@ -277,28 +342,30 @@ module Net
mid = 'auth_' + (authtype || 'cram_md5').to_s
raise ArgumentError, "wrong auth type #{authtype}"\
unless command().respond_to?(mid)
command().__send__ mid, user, secret
@command.__send__ mid, user, secret
end
end
private :do_start
def do_finish
disconn_command
disconn_socket
def finish
raise IOError, 'closing already closed SMTP session' unless @started
@command.quit if @command
@command = nil
@socket.close if @socket and not @socket.closed?
@socket = nil
@started = false
end
#
# SMTP operations
# SMTP wrapper
#
public
def send_mail( mailsrc, from_addr, *to_addrs )
do_ready from_addr, to_addrs.flatten
command().write_mail mailsrc
end
alias sendmail send_mail
alias sendmail send_mail # backward compatibility
def ready( from_addr, *to_addrs, &block )
do_ready from_addr, to_addrs.flatten
@ -313,45 +380,49 @@ module Net
command().rcpt to_addrs
end
def command
raise IOError, "closed session" unless @command
@command
end
end
SMTPSession = SMTP
class SMTPCommand < Command
class SMTPCommand
def initialize( sock )
super
atomic {
check_reply SuccessCode
}
@socket = sock
@in_critical_block = false
check_response(critical { recv_response() })
end
def inspect
"#<#{self.class} socket=#{@socket.inspect}>"
end
def helo( domain )
atomic {
getok sprintf('HELO %s', domain)
}
getok('HELO %s', domain)
end
def ehlo( domain )
atomic {
getok sprintf('EHLO %s', domain)
}
getok('EHLO %s', domain)
end
# "PLAIN" authentication [RFC2554]
def auth_plain( user, secret )
atomic {
getok sprintf('AUTH PLAIN %s',
["\0#{user}\0#{secret}"].pack('m').chomp)
}
res = critical { get_response('AUTH PLAIN %s',
["\0#{user}\0#{secret}"].pack('m').chomp) }
raise SMTPAuthenticationError, res unless /\A2../ === res
end
# "CRAM-MD5" authentication [RFC2195]
def auth_cram_md5( user, secret )
atomic {
rep = getok('AUTH CRAM-MD5', ContinueCode)
challenge = rep.msg.split(/ /)[1].unpack('m')[0]
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)
@ -363,86 +434,89 @@ module Net
tmp = Digest::MD5.digest(isecret + challenge)
tmp = Digest::MD5.hexdigest(osecret + tmp)
getok [user + ' ' + tmp].pack('m').gsub(/\s+/, '')
res = get_response([user + ' ' + tmp].pack('m').gsub(/\s+/, ''))
}
raise SMTPAuthenticationError, res unless /\A2../ === res
end
def mailfrom( fromaddr )
atomic {
getok sprintf('MAIL FROM:<%s>', fromaddr)
}
getok('MAIL FROM:<%s>', fromaddr)
end
def rcpt( toaddrs )
toaddrs.each do |i|
atomic {
getok sprintf('RCPT TO:<%s>', i)
}
getok('RCPT TO:<%s>', i)
end
end
def write_mail( src )
atomic {
getok 'DATA', ContinueCode
res = critical {
check_response(get_response('DATA'), true)
@socket.write_message src
check_reply SuccessCode
recv_response()
}
check_response(res)
end
def through_mail( &block )
atomic {
getok 'DATA', ContinueCode
res = critical {
check_response(get_response('DATA'), true)
@socket.through_message(&block)
check_reply SuccessCode
recv_response()
}
check_response(res)
end
def quit
atomic {
getok 'QUIT'
}
getok('QUIT')
end
private
def get_reply
arr = read_reply
stat = arr[0][0,3]
def getok( fmt, *args )
@socket.writeline sprintf(fmt, *args)
check_response(critical { recv_response() })
end
klass = case stat[0]
when ?2 then SuccessCode
when ?3 then ContinueCode
when ?4 then ServerErrorCode
def get_response( fmt, *args )
@socket.writeline sprintf(fmt, *args)
recv_response()
end
def recv_response
res = ''
while true
line = @socket.readline
res << line << "\n"
break unless line[3] == ?- # "210-PIPELINING"
end
res
end
def check_response( res, cont = false )
etype = case res[0]
when ?2 then nil
when ?3 then cont ? nil : ProtoUnknownError
when ?4 then ProtoServerError
when ?5 then
case stat[1]
when ?0 then SyntaxErrorCode
when ?3 then AuthErrorCode
when ?5 then FatalErrorCode
case res[1]
when ?0 then ProtoSyntaxError
when ?3 then ProtoAuthError
when ?5 then ProtoFatalError
end
end
klass ||= UnknownCode
Response.new(klass, stat, arr.join(''))
raise etype, res if etype
res
end
def read_reply
arr = []
while true
str = @socket.readline
break unless str[3] == ?- # "210-PIPELINING"
arr.push str
end
arr.push str
arr
def critical
return if @in_critical_block
@in_critical_block = true
result = yield()
@in_critical_block = false
result
end
end
# for backward compatibility
module NetPrivate
SMTPCommand = ::Net::SMTPCommand
end
end # module Net