summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2003-05-02 14:35:01 +0000
committeraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2003-05-02 14:35:01 +0000
commite3056c880364f5cb79eb9280b4d34b34c3eb4f93 (patch)
treeabadea47cc2c7fc951a7c0ee3ae21b9ae418f886
parentfd5f913f333dbdca213c2adffe5b680aa9915eac (diff)
* 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
-rw-r--r--ChangeLog25
-rw-r--r--lib/net/http.rb158
-rw-r--r--lib/net/pop.rb497
-rw-r--r--lib/net/protocol.rb343
-rw-r--r--lib/net/smtp.rb266
5 files changed, 651 insertions, 638 deletions
diff --git a/ChangeLog b/ChangeLog
index 27691e8641..e2db47cdbe 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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.
diff --git a/lib/net/http.rb b/lib/net/http.rb
index 7d425b1870..a88e69c0bb 100644
--- a/lib/net/http.rb
+++ b/lib/net/http.rb
@@ -562,10 +562,11 @@ module Net
class HTTPHeaderSyntaxError < StandardError; end
- class HTTP < Protocol
+ class HTTP
- HTTPVersion = '1.1'
+ 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,11 +1299,33 @@ module Net
end
-
###
- ### response
+ ### 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
+
+
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
diff --git a/lib/net/pop.rb b/lib/net/pop.rb
index 38fb7b6f76..9259750bb0 100644
--- a/lib/net/pop.rb
+++ b/lib/net/pop.rb
@@ -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
+
+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.
+
-== Net::POP3 class
+== 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
+
+ Revision = %q$Revision$.split[1]
+ #
+ # Class Parameters
+ #
- class POP3 < Protocol
+ def POP3.default_port
+ 110
+ end
- 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'
+ 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,40 +455,116 @@ module Net
end
#
- # connection
+ # Session management
#
- def initialize( addr, port = nil, apop = false )
- super addr, port
+ def POP3.start( address, port = nil,
+ account = nil, password = nil,
+ isapop = false, &block )
+ new(address, port, isapop).start(account, password, &block)
+ end
+
+ 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
- @apop = false
+ @nmails = nil
+ @bytes = nil
end
- private
+ 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
+ def command
+ raise IOError, 'POP session not opened yet' \
+ if not @socket or @socket.closed?
+ @command
+ end
+ private :command
+
#
- # POP operations
+ # POP protocol wrapper
#
- public
-
def mail_size
return @nmails if @nmails
@nmails, @bytes = command().stat
@@ -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
-
+ def recv_response
+ @socket.readline
+ end
- class APOPCommand < POP3Command
+ def check_response( res )
+ raise POPError, res unless /\A\+OK/i === res
+ res
+ end
- def initialize( sock )
- @stamp = super(sock).message.slice(/<.+>/) or
- raise ProtoAuthError.new("not APOP server: cannot login", nil)
+ def check_response_auth( res )
+ raise POPAuthenticationError, res unless /\A\+OK/i === res
+ res
end
- def auth( account, pass )
- atomic {
- @socket.writeline sprintf('APOP %s %s',
- account,
- Digest::MD5.hexdigest(@stamp + pass))
- check_reply_auth
- }
+ 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
diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb
index 9da1ad8909..fd85771dd5 100644
--- a/lib/net/protocol.rb
+++ b/lib/net/protocol.rb
@@ -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
diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb
index 89740411bd..b963204160 100644
--- a/lib/net/smtp.rb
+++ b/lib/net/smtp.rb
@@ -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
-
- def do_finish
- disconn_command
- disconn_socket
+ private :do_start
+
+ 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]
-
- klass = case stat[0]
- when ?2 then SuccessCode
- when ?3 then ContinueCode
- when ?4 then ServerErrorCode
- when ?5 then
- case stat[1]
- when ?0 then SyntaxErrorCode
- when ?3 then AuthErrorCode
- when ?5 then FatalErrorCode
- end
- end
- klass ||= UnknownCode
+ def getok( fmt, *args )
+ @socket.writeline sprintf(fmt, *args)
+ check_response(critical { recv_response() })
+ end
- Response.new(klass, stat, arr.join(''))
+ def get_response( fmt, *args )
+ @socket.writeline sprintf(fmt, *args)
+ recv_response()
end
- def read_reply
- arr = []
+ def recv_response
+ res = ''
while true
- str = @socket.readline
- break unless str[3] == ?- # "210-PIPELINING"
- arr.push str
+ line = @socket.readline
+ res << line << "\n"
+ break unless line[3] == ?- # "210-PIPELINING"
end
- arr.push str
-
- arr
+ res
end
- 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 res[1]
+ when ?0 then ProtoSyntaxError
+ when ?3 then ProtoAuthError
+ when ?5 then ProtoFatalError
+ end
+ end
+ raise etype, res if etype
+ res
+ end
+ def critical
+ return if @in_critical_block
+ @in_critical_block = true
+ result = yield()
+ @in_critical_block = false
+ result
+ end
- # for backward compatibility
- module NetPrivate
- SMTPCommand = ::Net::SMTPCommand
end
end # module Net