* 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:
parent
fd5f913f33
commit
e3056c8803
25
ChangeLog
25
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>
|
Fri May 2 18:17:37 2003 Yukihiro Matsumoto <matz@ruby-lang.org>
|
||||||
|
|
||||||
* compar.c (cmp_gt): raises ArgumentError when "<=>" give nil.
|
* compar.c (cmp_gt): raises ArgumentError when "<=>" give nil.
|
||||||
|
160
lib/net/http.rb
160
lib/net/http.rb
@ -562,11 +562,12 @@ module Net
|
|||||||
class HTTPHeaderSyntaxError < StandardError; end
|
class HTTPHeaderSyntaxError < StandardError; end
|
||||||
|
|
||||||
|
|
||||||
class HTTP < Protocol
|
class HTTP
|
||||||
|
|
||||||
|
Revision = %q$Revision$.split[1]
|
||||||
|
|
||||||
HTTPVersion = '1.1'
|
HTTPVersion = '1.1'
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# for backward compatibility
|
# for backward compatibility
|
||||||
#
|
#
|
||||||
@ -591,7 +592,6 @@ module Net
|
|||||||
end
|
end
|
||||||
private_class_method :setimplversion
|
private_class_method :setimplversion
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# short cut methods
|
# short cut methods
|
||||||
#
|
#
|
||||||
@ -644,13 +644,17 @@ module Net
|
|||||||
end
|
end
|
||||||
private_class_method :get_by_uri
|
private_class_method :get_by_uri
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# connection
|
# HTTP session management
|
||||||
#
|
#
|
||||||
|
|
||||||
protocol_param :default_port, '80'
|
def HTTP.default_port
|
||||||
protocol_param :socket_type, '::Net::InternetMessageIO'
|
80
|
||||||
|
end
|
||||||
|
|
||||||
|
def HTTP.socket_type
|
||||||
|
InternetMessageIO
|
||||||
|
end
|
||||||
|
|
||||||
class << HTTP
|
class << HTTP
|
||||||
def start( address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil, &block )
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize( addr, port = nil )
|
def initialize( address, port = nil )
|
||||||
super
|
@address = address
|
||||||
|
@port = port || HTTP.default_port
|
||||||
|
|
||||||
@curr_http_version = HTTPVersion
|
@curr_http_version = HTTPVersion
|
||||||
@seems_1_0_server = false
|
@seems_1_0_server = false
|
||||||
@close_on_empty_response = false
|
@close_on_empty_response = false
|
||||||
|
@socket = nil
|
||||||
|
@started = false
|
||||||
|
|
||||||
|
@open_timeout = 30
|
||||||
|
@read_timeout = 60
|
||||||
|
|
||||||
|
@debug_output = nil
|
||||||
end
|
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
|
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
|
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
|
end
|
||||||
|
private :do_start
|
||||||
|
|
||||||
def do_finish
|
def conn_address
|
||||||
disconn_socket
|
address()
|
||||||
end
|
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
|
# proxy
|
||||||
@ -784,9 +857,8 @@ module Net
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# http operations
|
# HTTP operations
|
||||||
#
|
#
|
||||||
|
|
||||||
public
|
public
|
||||||
@ -888,7 +960,8 @@ module Net
|
|||||||
|
|
||||||
def begin_transport( req )
|
def begin_transport( req )
|
||||||
if @socket.closed?
|
if @socket.closed?
|
||||||
reconn_socket
|
@socket.reopen @open_timeout
|
||||||
|
on_connect
|
||||||
end
|
end
|
||||||
if @seems_1_0_server
|
if @seems_1_0_server
|
||||||
req['connection'] = 'close'
|
req['connection'] = 'close'
|
||||||
@ -930,7 +1003,6 @@ module Net
|
|||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# utils
|
# utils
|
||||||
#
|
#
|
||||||
@ -954,7 +1026,7 @@ module Net
|
|||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
### header
|
### Header
|
||||||
###
|
###
|
||||||
|
|
||||||
module HTTPHeader
|
module HTTPHeader
|
||||||
@ -1012,6 +1084,7 @@ module Net
|
|||||||
def canonical( k )
|
def canonical( k )
|
||||||
k.split(/-/).map {|i| i.capitalize }.join('-')
|
k.split(/-/).map {|i| i.capitalize }.join('-')
|
||||||
end
|
end
|
||||||
|
private :canonical
|
||||||
|
|
||||||
def range
|
def range
|
||||||
s = @header['range'] or return nil
|
s = @header['range'] or return nil
|
||||||
@ -1101,7 +1174,7 @@ module Net
|
|||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
### request
|
### Request
|
||||||
###
|
###
|
||||||
|
|
||||||
class HTTPGenericRequest
|
class HTTPGenericRequest
|
||||||
@ -1188,14 +1261,12 @@ module Net
|
|||||||
|
|
||||||
|
|
||||||
class HTTPRequest < HTTPGenericRequest
|
class HTTPRequest < HTTPGenericRequest
|
||||||
|
|
||||||
def initialize( path, initheader = nil )
|
def initialize( path, initheader = nil )
|
||||||
super self.class::METHOD,
|
super self.class::METHOD,
|
||||||
self.class::REQUEST_HAS_BODY,
|
self.class::REQUEST_HAS_BODY,
|
||||||
self.class::RESPONSE_HAS_BODY,
|
self.class::RESPONSE_HAS_BODY,
|
||||||
path, initheader
|
path, initheader
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@ -1228,10 +1299,32 @@ module Net
|
|||||||
end
|
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
|
class HTTPResponse
|
||||||
# predefine HTTPResponse class to allow inheritance
|
# predefine HTTPResponse class to allow inheritance
|
||||||
@ -1245,30 +1338,29 @@ module Net
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
class HTTPUnknownResponse < HTTPResponse
|
class HTTPUnknownResponse < HTTPResponse
|
||||||
HAS_BODY = true
|
HAS_BODY = true
|
||||||
EXCEPTION_TYPE = ProtocolError
|
EXCEPTION_TYPE = HTTPError
|
||||||
end
|
end
|
||||||
class HTTPInformation < HTTPResponse # 1xx
|
class HTTPInformation < HTTPResponse # 1xx
|
||||||
HAS_BODY = false
|
HAS_BODY = false
|
||||||
EXCEPTION_TYPE = ProtocolError
|
EXCEPTION_TYPE = HTTPError
|
||||||
end
|
end
|
||||||
class HTTPSuccess < HTTPResponse # 2xx
|
class HTTPSuccess < HTTPResponse # 2xx
|
||||||
HAS_BODY = true
|
HAS_BODY = true
|
||||||
EXCEPTION_TYPE = ProtocolError
|
EXCEPTION_TYPE = HTTPError
|
||||||
end
|
end
|
||||||
class HTTPRedirection < HTTPResponse # 3xx
|
class HTTPRedirection < HTTPResponse # 3xx
|
||||||
HAS_BODY = true
|
HAS_BODY = true
|
||||||
EXCEPTION_TYPE = ProtoRetriableError
|
EXCEPTION_TYPE = HTTPRetriableError
|
||||||
end
|
end
|
||||||
class HTTPClientError < HTTPResponse # 4xx
|
class HTTPClientError < HTTPResponse # 4xx
|
||||||
HAS_BODY = true
|
HAS_BODY = true
|
||||||
EXCEPTION_TYPE = ProtoServerError # for backward compatibility
|
EXCEPTION_TYPE = HTTPServerException # for backward compatibility
|
||||||
end
|
end
|
||||||
class HTTPServerError < HTTPResponse # 5xx
|
class HTTPServerError < HTTPResponse # 5xx
|
||||||
HAS_BODY = true
|
HAS_BODY = true
|
||||||
EXCEPTION_TYPE = ProtoFatalError # for backward compatibility
|
EXCEPTION_TYPE = HTTPFatalError # for backward compatibility
|
||||||
end
|
end
|
||||||
|
|
||||||
class HTTPContinue < HTTPInformation # 100
|
class HTTPContinue < HTTPInformation # 100
|
||||||
@ -1649,12 +1741,6 @@ module Net
|
|||||||
|
|
||||||
# for backward compatibility
|
# for backward compatibility
|
||||||
|
|
||||||
module NetPrivate
|
|
||||||
HTTPResponse = ::Net::HTTPResponse
|
|
||||||
HTTPGenericRequest = ::Net::HTTPGenericRequest
|
|
||||||
HTTPRequest = ::Net::HTTPRequest
|
|
||||||
HTTPHeader = ::Net::HTTPHeader
|
|
||||||
end
|
|
||||||
HTTPInformationCode = HTTPInformation
|
HTTPInformationCode = HTTPInformation
|
||||||
HTTPSuccessCode = HTTPSuccess
|
HTTPSuccessCode = HTTPSuccess
|
||||||
HTTPRedirectionCode = HTTPRedirection
|
HTTPRedirectionCode = HTTPRedirection
|
||||||
|
505
lib/net/pop.rb
505
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.
|
(3) close POP session by calling POP3#finish or use block form #start.
|
||||||
|
|
||||||
This example is using block form #start to close the session.
|
This example is using block form #start to close the session.
|
||||||
|
|
||||||
=== Enshort Code
|
=== Enshort Code
|
||||||
|
|
||||||
The example above is very verbose. You can enshort code by using
|
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'
|
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 = Net::POP3.APOP($isapop).new('apop.server.address', 110)
|
||||||
pop.start(YourAccount', 'YourPassword') {|pop|
|
pop.start(YourAccount', 'YourPassword') {|pop|
|
||||||
# Rest code is same.
|
# 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
|
=== Class Methods
|
||||||
|
|
||||||
: new( address, port = 110, apop = false )
|
: new( address, port = 110, isapop = false )
|
||||||
creates a new Net::POP3 object.
|
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, isapop = false )
|
||||||
: start( address, port = 110, account, password ) {|pop| .... }
|
: start( address, port = 110, account, password, isapop = false ) {|pop| .... }
|
||||||
equals to Net::POP3.new( address, port ).start( account, password )
|
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|
|
pop.each_mail do |m|
|
||||||
file.write m.pop
|
file.write m.pop
|
||||||
m.delete
|
m.delete
|
||||||
@ -163,17 +186,17 @@ You can use utility method, Net::POP3.APOP(). Example:
|
|||||||
returns Net::POP3 class object if false.
|
returns Net::POP3 class object if false.
|
||||||
Use this method like:
|
Use this method like:
|
||||||
|
|
||||||
# example 1
|
# Example 1
|
||||||
pop = Net::POP3::APOP($isapop).new( addr, port )
|
pop = Net::POP3::APOP($isapop).new( addr, port )
|
||||||
|
|
||||||
# example 2
|
# Example 2
|
||||||
Net::POP3::APOP($isapop).start( addr, port ) {|pop|
|
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.
|
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|
|
Net::POP3.start( address, port, account, password ) {|pop|
|
||||||
pop.each_mail do |m|
|
pop.each_mail do |m|
|
||||||
@ -181,29 +204,33 @@ You can use utility method, Net::POP3.APOP(). Example:
|
|||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
# example
|
This method raises POPAuthenticationError if authentication is failed.
|
||||||
|
|
||||||
|
# Typical usage
|
||||||
Net::POP3.foreach( 'your.pop.server', 110,
|
Net::POP3.foreach( 'your.pop.server', 110,
|
||||||
'YourAccount', 'YourPassword' ) do |m|
|
'YourAccount', 'YourPassword' ) do |m|
|
||||||
file.write m.pop
|
file.write m.pop
|
||||||
m.delete if $DELETE
|
m.delete if $DELETE
|
||||||
end
|
end
|
||||||
|
|
||||||
: delete_all( address, port = 110, account, password )
|
: delete_all( address, port = 110, account, password, isapop = false )
|
||||||
: delete_all( address, port = 110, account, password ) {|mail| .... }
|
: delete_all( address, port = 110, account, password, isapop = false ) {|mail| .... }
|
||||||
starts POP3 session and delete all mails.
|
starts POP3 session and delete all mails.
|
||||||
If block is given, iterates for each POPMail object before delete.
|
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|
|
Net::POP3.delete_all( addr, nil, 'YourAccount', 'YourPassword' ) do |m|
|
||||||
m.pop file
|
m.pop file
|
||||||
end
|
end
|
||||||
|
|
||||||
: auth_only( address, port = 110, account, password )
|
: auth_only( address, port = 110, account, password, isapop = false )
|
||||||
(just for POP-before-SMTP)
|
(just for POP-before-SMTP)
|
||||||
opens POP3 session and does autholize and quit.
|
opens POP3 session and does autholize and quit.
|
||||||
This method must not be called while POP3 session is opened.
|
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',
|
Net::POP3.auth_only( 'your.pop3.server',
|
||||||
nil, # using default (110)
|
nil, # using default (110)
|
||||||
'YourAccount',
|
'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
|
When called with block, gives a POP3 object to block and
|
||||||
closes the session after block call finish.
|
closes the session after block call finish.
|
||||||
|
|
||||||
: active?
|
This method raises POPAuthenticationError if authentication is failed.
|
||||||
|
|
||||||
|
: started?
|
||||||
true if POP3 session is started.
|
true if POP3 session is started.
|
||||||
|
|
||||||
: address
|
: address
|
||||||
@ -245,44 +274,62 @@ You can use utility method, Net::POP3.APOP(). Example:
|
|||||||
|
|
||||||
: mails
|
: mails
|
||||||
an array of Net::POPMail objects.
|
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_mail {|popmail| .... }
|
||||||
: each {|popmail| .... }
|
: each {|popmail| .... }
|
||||||
is equals to "pop3.mails.each"
|
is equals to "pop3.mails.each"
|
||||||
|
|
||||||
|
This method raises POPError if any problem happend.
|
||||||
|
|
||||||
: delete_all
|
: delete_all
|
||||||
: delete_all {|popmail| .... }
|
: delete_all {|popmail| .... }
|
||||||
deletes all mails on server.
|
deletes all mails on server.
|
||||||
If called with block, gives mails to the block before deleting.
|
If called with block, gives mails to the block before deleting.
|
||||||
|
|
||||||
# example
|
# Example
|
||||||
n = 1
|
n = 1
|
||||||
pop.delete_all do |m|
|
pop.delete_all do |m|
|
||||||
File.open("inbox/#{n}") {|f| f.write m.pop }
|
File.open("inbox/#{n}") {|f| f.write m.pop }
|
||||||
n += 1
|
n += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
This method raises POPError if any problem happend.
|
||||||
|
|
||||||
: auth_only( account, password )
|
: auth_only( account, password )
|
||||||
(just for POP-before-SMTP)
|
(just for POP-before-SMTP)
|
||||||
opens POP3 session and does autholize and quit.
|
|
||||||
This method must not be called while POP3 session is opened.
|
opens POP3 session, does authorization, then quit.
|
||||||
# example
|
You must not call this method after POP3 session is opened.
|
||||||
pop = Net::POP3.new( 'your.pop3.server' )
|
|
||||||
pop.auth_only 'YourAccount', 'YourPassword'
|
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
|
||||||
reset the session. All "deleted mark" are removed.
|
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.
|
This class defines no new methods.
|
||||||
Only difference from POP3 is using APOP authentification.
|
Only difference from POP3 is using APOP authentification.
|
||||||
|
|
||||||
=== Super Class
|
=== Super Class
|
||||||
|
|
||||||
Net::POP3
|
Net::POP3
|
||||||
|
|
||||||
== Net::POPMail
|
|
||||||
|
== class Net::POPMail
|
||||||
|
|
||||||
A class of mail which exists on POP server.
|
A class of mail which exists on POP server.
|
||||||
|
|
||||||
@ -291,7 +338,9 @@ A class of mail which exists on POP server.
|
|||||||
: pop( dest = '' )
|
: pop( dest = '' )
|
||||||
This method fetches a mail and write to 'dest' using '<<' method.
|
This method fetches a mail and write to 'dest' using '<<' method.
|
||||||
|
|
||||||
# example
|
This method raises POPError if any problem happend.
|
||||||
|
|
||||||
|
# Typical usage
|
||||||
allmails = nil
|
allmails = nil
|
||||||
POP3.start( 'your.pop3.server', 110,
|
POP3.start( 'your.pop3.server', 110,
|
||||||
'YourAccount, 'YourPassword' ) {|pop|
|
'YourAccount, 'YourPassword' ) {|pop|
|
||||||
@ -301,7 +350,9 @@ A class of mail which exists on POP server.
|
|||||||
: pop {|str| .... }
|
: pop {|str| .... }
|
||||||
gives the block part strings of a mail.
|
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.start( 'localhost', 110 ) {|pop3|
|
||||||
pop3.each_mail do |m|
|
pop3.each_mail do |m|
|
||||||
m.pop do |str|
|
m.pop do |str|
|
||||||
@ -311,20 +362,32 @@ A class of mail which exists on POP server.
|
|||||||
}
|
}
|
||||||
|
|
||||||
: header
|
: header
|
||||||
This method fetches only mail header.
|
fetches only mail header.
|
||||||
|
|
||||||
|
This method raises POPError if any problem happend.
|
||||||
|
|
||||||
: top( lines )
|
: 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
|
: delete
|
||||||
deletes mail on server.
|
deletes mail on server.
|
||||||
|
|
||||||
|
This method raises POPError if any problem happend.
|
||||||
|
|
||||||
: size
|
: size
|
||||||
mail size (bytes)
|
mail size (bytes)
|
||||||
|
|
||||||
: deleted?
|
: deleted?
|
||||||
true if mail was 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
|
=end
|
||||||
|
|
||||||
require 'net/protocol'
|
require 'net/protocol'
|
||||||
@ -333,38 +396,55 @@ require 'digest/md5'
|
|||||||
|
|
||||||
module Net
|
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'
|
Revision = %q$Revision$.split[1]
|
||||||
protocol_param :command_type, '::Net::POP3Command'
|
|
||||||
protocol_param :apop_command_type, '::Net::APOPCommand'
|
#
|
||||||
protocol_param :mail_type, '::Net::POPMail'
|
# Class Parameters
|
||||||
protocol_param :socket_type, '::Net::InternetMessageIO'
|
#
|
||||||
|
|
||||||
|
def POP3.default_port
|
||||||
|
110
|
||||||
|
end
|
||||||
|
|
||||||
|
def POP3.socket_type
|
||||||
|
Net::InternetMessageIO
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Utilities
|
||||||
|
#
|
||||||
|
|
||||||
def POP3.APOP( isapop )
|
def POP3.APOP( isapop )
|
||||||
isapop ? APOP : POP3
|
isapop ? APOP : POP3
|
||||||
end
|
end
|
||||||
|
|
||||||
def POP3.foreach( address, port = nil,
|
def POP3.foreach( address, port = nil,
|
||||||
account = nil, password = nil, &block )
|
account = nil, password = nil,
|
||||||
start(address, port, account, password) {|pop|
|
isapop = false, &block )
|
||||||
|
start(address, port, account, password, isapop) {|pop|
|
||||||
pop.each_mail(&block)
|
pop.each_mail(&block)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def POP3.delete_all( address, port = nil,
|
def POP3.delete_all( address, port = nil,
|
||||||
account = nil, password = nil, &block )
|
account = nil, password = nil,
|
||||||
start(address, port, account, password) {|pop|
|
isapop = false, &block )
|
||||||
|
start(address, port, account, password, isapop) {|pop|
|
||||||
pop.delete_all(&block)
|
pop.delete_all(&block)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def POP3.auth_only( address, port = nil,
|
def POP3.auth_only( address, port = nil,
|
||||||
account = nil, password = nil )
|
account = nil, password = nil,
|
||||||
new(address, port).auth_only account, password
|
isapop = false )
|
||||||
|
new(address, port, isapop).auth_only account, password
|
||||||
end
|
end
|
||||||
|
|
||||||
def auth_only( account, password )
|
def auth_only( account, password )
|
||||||
@ -375,39 +455,115 @@ module Net
|
|||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# connection
|
# Session management
|
||||||
#
|
#
|
||||||
|
|
||||||
def initialize( addr, port = nil, apop = false )
|
def POP3.start( address, port = nil,
|
||||||
super addr, port
|
account = nil, password = nil,
|
||||||
@mails = nil
|
isapop = false, &block )
|
||||||
@apop = false
|
new(address, port, isapop).start(account, password, &block)
|
||||||
end
|
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 )
|
def do_start( account, password )
|
||||||
conn_socket
|
@socket = self.class.socket_type.open(@address, @port,
|
||||||
conn_command
|
@open_timeout, @read_timeout, @debug_output)
|
||||||
@command.auth account, password
|
on_connect
|
||||||
|
@command = POP3Command.new(@socket)
|
||||||
|
if apop?
|
||||||
|
@command.apop account, password
|
||||||
|
else
|
||||||
|
@command.auth account, password
|
||||||
|
end
|
||||||
|
@started = true
|
||||||
end
|
end
|
||||||
|
private :do_start
|
||||||
|
|
||||||
def conn_command
|
def on_connect
|
||||||
@command = (@apop ? self.class.apop_command_type :
|
|
||||||
self.class.command_type ).new(socket())
|
|
||||||
end
|
end
|
||||||
|
private :on_connect
|
||||||
|
|
||||||
def do_finish
|
def finish
|
||||||
|
raise IOError, 'already closed POP session' unless @started
|
||||||
@mails = nil
|
@mails = nil
|
||||||
disconn_command
|
@command.quit if @command
|
||||||
disconn_socket
|
@command = nil
|
||||||
|
@socket.close if @socket and not @socket.closed?
|
||||||
|
@socket = nil
|
||||||
|
@started = false
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
def command
|
||||||
# POP operations
|
raise IOError, 'POP session not opened yet' \
|
||||||
#
|
if not @socket or @socket.closed?
|
||||||
|
@command
|
||||||
|
end
|
||||||
|
private :command
|
||||||
|
|
||||||
public
|
#
|
||||||
|
# POP protocol wrapper
|
||||||
|
#
|
||||||
|
|
||||||
def mail_size
|
def mail_size
|
||||||
return @nmails if @nmails
|
return @nmails if @nmails
|
||||||
@ -422,21 +578,17 @@ module Net
|
|||||||
end
|
end
|
||||||
|
|
||||||
def mails
|
def mails
|
||||||
return @mails if @mails
|
return @mails.dup if @mails
|
||||||
if mail_size() == 0
|
if mail_size() == 0
|
||||||
# some popd raises error for LIST on the empty mailbox.
|
# some popd raises error for LIST on the empty mailbox.
|
||||||
@mails = []
|
@mails = []
|
||||||
return @mails
|
return []
|
||||||
end
|
end
|
||||||
|
|
||||||
mails = []
|
@mails = command().list.map {|num, size|
|
||||||
mailclass = self.class.mail_type
|
POPMail.new(num, size, self, command())
|
||||||
command().list.each_with_index do |size,idx|
|
}
|
||||||
mails.push mailclass.new(idx, size, command()) if size
|
@mails.dup
|
||||||
end
|
|
||||||
@mails = mails.freeze
|
|
||||||
|
|
||||||
@mails
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def each_mail( &block )
|
def each_mail( &block )
|
||||||
@ -461,25 +613,23 @@ module Net
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def command
|
# internal use only (called from POPMail#uidl).
|
||||||
io_check
|
def set_all_uids
|
||||||
super
|
command().uidl.each do |num, uid|
|
||||||
end
|
@mails.find {|m| m.number == num }.uid = uid
|
||||||
|
end
|
||||||
def io_check
|
|
||||||
raise IOError, 'POP session is not opened yet'\
|
|
||||||
if not socket() or socket().closed?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# aliases
|
||||||
POP = POP3
|
POP = POP3
|
||||||
POPSession = POP3
|
POPSession = POP3
|
||||||
POP3Session = POP3
|
POP3Session = POP3
|
||||||
|
|
||||||
class APOP < POP3
|
class APOP < POP3
|
||||||
def APOP.command_type
|
def apop?
|
||||||
APOPCommand
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -488,171 +638,188 @@ module Net
|
|||||||
|
|
||||||
class POPMail
|
class POPMail
|
||||||
|
|
||||||
def initialize( n, s, cmd )
|
def initialize( num, size, pop, cmd )
|
||||||
@num = n
|
@number = num
|
||||||
@size = s
|
@size = size
|
||||||
|
@pop = pop
|
||||||
@command = cmd
|
@command = cmd
|
||||||
|
|
||||||
@deleted = false
|
@deleted = false
|
||||||
|
@uid = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attr_reader :number
|
||||||
attr_reader :size
|
attr_reader :size
|
||||||
|
|
||||||
def inspect
|
def inspect
|
||||||
"#<#{self.class} #{@num}#{@deleted ? ' deleted' : ''}>"
|
"#<#{self.class} #{@number}#{@deleted ? ' deleted' : ''}>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def pop( dest = '', &block )
|
def pop( dest = '', &block )
|
||||||
dest = ReadAdapter.new(block) if block
|
@command.retr(@number, (block ? ReadAdapter.new(block) : dest))
|
||||||
@command.retr @num, dest
|
|
||||||
end
|
end
|
||||||
|
|
||||||
alias all pop
|
alias all pop # backward compatibility
|
||||||
alias mail pop
|
alias mail pop # backward compatibility
|
||||||
|
|
||||||
def top( lines, dest = '' )
|
def top( lines, dest = '' )
|
||||||
@command.top @num, lines, dest
|
@command.top(@number, lines, dest)
|
||||||
end
|
end
|
||||||
|
|
||||||
def header( dest = '' )
|
def header( dest = '' )
|
||||||
top 0, dest
|
top(0, dest)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete
|
def delete
|
||||||
@command.dele @num
|
@command.dele @number
|
||||||
@deleted = true
|
@deleted = true
|
||||||
end
|
end
|
||||||
|
|
||||||
alias delete! delete
|
alias delete! delete # backward compatibility
|
||||||
|
|
||||||
def deleted?
|
def deleted?
|
||||||
@deleted
|
@deleted
|
||||||
end
|
end
|
||||||
|
|
||||||
def uidl
|
def unique_id
|
||||||
@command.uidl @num
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
class POP3Command < Command
|
class POP3Command
|
||||||
|
|
||||||
def initialize( sock )
|
def initialize( sock )
|
||||||
super
|
@socket = sock
|
||||||
atomic {
|
@in_critical_block = false
|
||||||
check_reply SuccessCode
|
res = check_response(critical { recv_response() })
|
||||||
}
|
@apop_stamp = res.slice(/<.+>/)
|
||||||
end
|
end
|
||||||
|
|
||||||
def auth( account, pass )
|
def inspect
|
||||||
atomic {
|
"#<#{self.class} socket=#{@socket}>"
|
||||||
@socket.writeline 'USER ' + account
|
end
|
||||||
check_reply_auth
|
|
||||||
|
|
||||||
@socket.writeline 'PASS ' + pass
|
def auth( account, password )
|
||||||
check_reply_auth
|
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
|
end
|
||||||
|
|
||||||
def list
|
def list
|
||||||
atomic {
|
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 BadResponse, "bad response: #{line}"
|
raise POPBadResponse, "bad response: #{line}"
|
||||||
list[m[1].to_i] = m[2].to_i
|
list.push [m[1].to_i, m[2].to_i]
|
||||||
end
|
end
|
||||||
return list
|
list
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def stat
|
def stat
|
||||||
atomic {
|
res = check_response(critical { get_response('STAT') })
|
||||||
@socket.writeline 'STAT'
|
m = /\A\+OK\s+(\d+)\s+(\d+)/.match(res) or
|
||||||
line = @socket.readline
|
raise POPBadResponse, "wrong response format: #{res}"
|
||||||
m = /\A\+OK (\d+)[ \t]+(\d+)/.match(line) or
|
[m[1].to_i, m[2].to_i]
|
||||||
raise BadResponseError, "illegal response: #{line}"
|
|
||||||
return [m[1].to_i, m[2].to_i]
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def rset
|
def rset
|
||||||
atomic {
|
check_reply(critical { get_response 'RSET' })
|
||||||
getok 'RSET'
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def top( num, lines = 0, dest = '' )
|
def top( num, lines = 0, dest = '' )
|
||||||
atomic {
|
critical {
|
||||||
getok sprintf('TOP %d %d', num, lines)
|
getok('TOP %d %d', num, lines)
|
||||||
@socket.read_message_to dest
|
@socket.read_message_to(dest)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def retr( num, dest = '' )
|
def retr( num, dest = '' )
|
||||||
atomic {
|
critical {
|
||||||
getok sprintf('RETR %d', num)
|
getok('RETR %d', num)
|
||||||
@socket.read_message_to dest
|
@socket.read_message_to dest
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def dele( num )
|
def dele( num )
|
||||||
atomic {
|
check_response(critical { get_response('DELE %d', num) })
|
||||||
getok sprintf('DELE %d', num)
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def uidl( num )
|
def uidl( num = nil )
|
||||||
atomic {
|
if num
|
||||||
getok(sprintf('UIDL %d', num)).message.split(/ /)[1]
|
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
|
end
|
||||||
|
|
||||||
def quit
|
def quit
|
||||||
atomic {
|
check_response(critical { get_response('QUIT') })
|
||||||
getok 'QUIT'
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def check_reply_auth
|
def getok( *reqs )
|
||||||
begin
|
@socket.writeline sprintf(*reqs)
|
||||||
return check_reply(SuccessCode)
|
check_response(recv_response())
|
||||||
rescue ProtocolError => err
|
|
||||||
raise ProtoAuthError.new('Fail to POP authentication', err.response)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_reply
|
def get_response( *reqs )
|
||||||
str = @socket.readline
|
@socket.writeline sprintf(*reqs)
|
||||||
if /\A\+/ === str
|
recv_response()
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
def recv_response
|
||||||
|
@socket.readline
|
||||||
|
|
||||||
class APOPCommand < POP3Command
|
|
||||||
|
|
||||||
def initialize( sock )
|
|
||||||
@stamp = super(sock).message.slice(/<.+>/) or
|
|
||||||
raise ProtoAuthError.new("not APOP server: cannot login", nil)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def auth( account, pass )
|
def check_response( res )
|
||||||
atomic {
|
raise POPError, res unless /\A\+OK/i === res
|
||||||
@socket.writeline sprintf('APOP %s %s',
|
res
|
||||||
account,
|
end
|
||||||
Digest::MD5.hexdigest(@stamp + pass))
|
|
||||||
check_reply_auth
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
= net/protocol.rb
|
= 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>
|
written & maintained by Minero Aoki <aamine@loveruby.net>
|
||||||
|
|
||||||
@ -23,211 +24,6 @@ require 'timeout'
|
|||||||
|
|
||||||
module Net
|
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 ProtocolError < StandardError; end
|
||||||
class ProtoSyntaxError < ProtocolError; end
|
class ProtoSyntaxError < ProtocolError; end
|
||||||
class ProtoFatalError < ProtocolError; end
|
class ProtoFatalError < ProtocolError; end
|
||||||
@ -238,129 +34,6 @@ module Net
|
|||||||
class ProtoRetriableError < ProtocolError; end
|
class ProtoRetriableError < ProtocolError; end
|
||||||
ProtocRetryError = ProtoRetriableError
|
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
|
class InternetMessageIO
|
||||||
|
|
||||||
@ -765,16 +438,4 @@ module Net
|
|||||||
|
|
||||||
end
|
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
|
end # module Net
|
||||||
|
266
lib/net/smtp.rb
266
lib/net/smtp.rb
@ -44,7 +44,7 @@ executed.
|
|||||||
|
|
||||||
require 'net/smtp'
|
require 'net/smtp'
|
||||||
Net::SMTP.start('your.smtp.server', 25) {|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
|
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
|
authentication by using AUTH command. :plain or :cram_md5 is
|
||||||
allowed for AUTHTYPE.
|
allowed for AUTHTYPE.
|
||||||
|
|
||||||
: active?
|
: started?
|
||||||
true if SMTP session is started.
|
true if SMTP session is started.
|
||||||
|
|
||||||
|
: esmtp?
|
||||||
|
true if the SMTP object uses ESMTP.
|
||||||
|
|
||||||
|
: esmtp=(b)
|
||||||
|
set wheather SMTP should use ESMTP.
|
||||||
|
|
||||||
: address
|
: address
|
||||||
the address to connect
|
the address to connect
|
||||||
|
|
||||||
@ -209,14 +215,15 @@ the SMTP session by inspecting HELO domain.
|
|||||||
== Exceptions
|
== Exceptions
|
||||||
|
|
||||||
SMTP objects raise these exceptions:
|
SMTP objects raise these exceptions:
|
||||||
|
|
||||||
: Net::ProtoSyntaxError
|
: Net::ProtoSyntaxError
|
||||||
syntax error (errno.500)
|
Syntax error (errno.500)
|
||||||
: Net::ProtoFatalError
|
: Net::ProtoFatalError
|
||||||
fatal error (errno.550)
|
Fatal error (errno.550)
|
||||||
: Net::ProtoUnknownError
|
: Net::ProtoUnknownError
|
||||||
unknown error. (is probably bug)
|
Unknown error. (is probably bug)
|
||||||
: Net::ProtoServerBusy
|
: Net::ProtoServerBusy
|
||||||
temporary error (errno.420/450)
|
Temporal error (errno.420/450)
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
@ -226,16 +233,31 @@ require 'digest/md5'
|
|||||||
|
|
||||||
module Net
|
module Net
|
||||||
|
|
||||||
class SMTP < Protocol
|
class SMTP
|
||||||
|
|
||||||
protocol_param :default_port, '25'
|
Revision = %q$Revision$.split[1]
|
||||||
protocol_param :command_type, '::Net::SMTPCommand'
|
|
||||||
protocol_param :socket_type, '::Net::InternetMessageIO'
|
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
|
@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
|
end
|
||||||
|
|
||||||
def esmtp?
|
def esmtp?
|
||||||
@ -248,27 +270,70 @@ module Net
|
|||||||
|
|
||||||
alias esmtp esmtp?
|
alias esmtp esmtp?
|
||||||
|
|
||||||
private
|
attr_reader :address
|
||||||
|
attr_reader :port
|
||||||
|
|
||||||
def do_start( helo = 'localhost.localdomain',
|
attr_accessor :open_timeout
|
||||||
user = nil, secret = nil, authtype = nil )
|
attr_reader :read_timeout
|
||||||
conn_socket
|
|
||||||
conn_command
|
|
||||||
|
|
||||||
|
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
|
begin
|
||||||
if @esmtp
|
if @esmtp
|
||||||
command().ehlo helo
|
@command.ehlo helo
|
||||||
else
|
else
|
||||||
command().helo helo
|
@command.helo helo
|
||||||
end
|
end
|
||||||
rescue ProtocolError
|
rescue ProtocolError
|
||||||
if @esmtp
|
if @esmtp
|
||||||
@esmtp = false
|
@esmtp = false
|
||||||
command().error_ok
|
@command.error_ok
|
||||||
retry
|
retry
|
||||||
else
|
|
||||||
raise
|
|
||||||
end
|
end
|
||||||
|
raise
|
||||||
end
|
end
|
||||||
|
|
||||||
if user or secret
|
if user or secret
|
||||||
@ -277,28 +342,30 @@ module Net
|
|||||||
mid = 'auth_' + (authtype || 'cram_md5').to_s
|
mid = 'auth_' + (authtype || 'cram_md5').to_s
|
||||||
raise ArgumentError, "wrong auth type #{authtype}"\
|
raise ArgumentError, "wrong auth type #{authtype}"\
|
||||||
unless command().respond_to?(mid)
|
unless command().respond_to?(mid)
|
||||||
command().__send__ mid, user, secret
|
@command.__send__ mid, user, secret
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
private :do_start
|
||||||
|
|
||||||
def do_finish
|
def finish
|
||||||
disconn_command
|
raise IOError, 'closing already closed SMTP session' unless @started
|
||||||
disconn_socket
|
@command.quit if @command
|
||||||
|
@command = nil
|
||||||
|
@socket.close if @socket and not @socket.closed?
|
||||||
|
@socket = nil
|
||||||
|
@started = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# SMTP operations
|
# SMTP wrapper
|
||||||
#
|
#
|
||||||
|
|
||||||
public
|
|
||||||
|
|
||||||
def send_mail( mailsrc, from_addr, *to_addrs )
|
def send_mail( mailsrc, from_addr, *to_addrs )
|
||||||
do_ready from_addr, to_addrs.flatten
|
do_ready from_addr, to_addrs.flatten
|
||||||
command().write_mail mailsrc
|
command().write_mail mailsrc
|
||||||
end
|
end
|
||||||
|
|
||||||
alias sendmail send_mail
|
alias sendmail send_mail # backward compatibility
|
||||||
|
|
||||||
def ready( from_addr, *to_addrs, &block )
|
def ready( from_addr, *to_addrs, &block )
|
||||||
do_ready from_addr, to_addrs.flatten
|
do_ready from_addr, to_addrs.flatten
|
||||||
@ -313,45 +380,49 @@ module Net
|
|||||||
command().rcpt to_addrs
|
command().rcpt to_addrs
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def command
|
||||||
|
raise IOError, "closed session" unless @command
|
||||||
|
@command
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
SMTPSession = SMTP
|
SMTPSession = SMTP
|
||||||
|
|
||||||
|
|
||||||
class SMTPCommand < Command
|
class SMTPCommand
|
||||||
|
|
||||||
def initialize( sock )
|
def initialize( sock )
|
||||||
super
|
@socket = sock
|
||||||
atomic {
|
@in_critical_block = false
|
||||||
check_reply SuccessCode
|
check_response(critical { recv_response() })
|
||||||
}
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
"#<#{self.class} socket=#{@socket.inspect}>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def helo( domain )
|
def helo( domain )
|
||||||
atomic {
|
getok('HELO %s', domain)
|
||||||
getok sprintf('HELO %s', domain)
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def ehlo( domain )
|
def ehlo( domain )
|
||||||
atomic {
|
getok('EHLO %s', domain)
|
||||||
getok sprintf('EHLO %s', domain)
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# "PLAIN" authentication [RFC2554]
|
# "PLAIN" authentication [RFC2554]
|
||||||
def auth_plain( user, secret )
|
def auth_plain( user, secret )
|
||||||
atomic {
|
res = critical { get_response('AUTH PLAIN %s',
|
||||||
getok sprintf('AUTH PLAIN %s',
|
["\0#{user}\0#{secret}"].pack('m').chomp) }
|
||||||
["\0#{user}\0#{secret}"].pack('m').chomp)
|
raise SMTPAuthenticationError, res unless /\A2../ === res
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# "CRAM-MD5" authentication [RFC2195]
|
# "CRAM-MD5" authentication [RFC2195]
|
||||||
def auth_cram_md5( user, secret )
|
def auth_cram_md5( user, secret )
|
||||||
atomic {
|
res = nil
|
||||||
rep = getok('AUTH CRAM-MD5', ContinueCode)
|
critical {
|
||||||
challenge = rep.msg.split(/ /)[1].unpack('m')[0]
|
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
|
secret = Digest::MD5.digest(secret) if secret.size > 64
|
||||||
|
|
||||||
isecret = secret + "\0" * (64 - secret.size)
|
isecret = secret + "\0" * (64 - secret.size)
|
||||||
@ -363,86 +434,89 @@ module Net
|
|||||||
tmp = Digest::MD5.digest(isecret + challenge)
|
tmp = Digest::MD5.digest(isecret + challenge)
|
||||||
tmp = Digest::MD5.hexdigest(osecret + tmp)
|
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
|
end
|
||||||
|
|
||||||
def mailfrom( fromaddr )
|
def mailfrom( fromaddr )
|
||||||
atomic {
|
getok('MAIL FROM:<%s>', fromaddr)
|
||||||
getok sprintf('MAIL FROM:<%s>', fromaddr)
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def rcpt( toaddrs )
|
def rcpt( toaddrs )
|
||||||
toaddrs.each do |i|
|
toaddrs.each do |i|
|
||||||
atomic {
|
getok('RCPT TO:<%s>', i)
|
||||||
getok sprintf('RCPT TO:<%s>', i)
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def write_mail( src )
|
def write_mail( src )
|
||||||
atomic {
|
res = critical {
|
||||||
getok 'DATA', ContinueCode
|
check_response(get_response('DATA'), true)
|
||||||
@socket.write_message src
|
@socket.write_message src
|
||||||
check_reply SuccessCode
|
recv_response()
|
||||||
}
|
}
|
||||||
|
check_response(res)
|
||||||
end
|
end
|
||||||
|
|
||||||
def through_mail( &block )
|
def through_mail( &block )
|
||||||
atomic {
|
res = critical {
|
||||||
getok 'DATA', ContinueCode
|
check_response(get_response('DATA'), true)
|
||||||
@socket.through_message(&block)
|
@socket.through_message(&block)
|
||||||
check_reply SuccessCode
|
recv_response()
|
||||||
}
|
}
|
||||||
|
check_response(res)
|
||||||
end
|
end
|
||||||
|
|
||||||
def quit
|
def quit
|
||||||
atomic {
|
getok('QUIT')
|
||||||
getok 'QUIT'
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def get_reply
|
def getok( fmt, *args )
|
||||||
arr = read_reply
|
@socket.writeline sprintf(fmt, *args)
|
||||||
stat = arr[0][0,3]
|
check_response(critical { recv_response() })
|
||||||
|
end
|
||||||
|
|
||||||
klass = case stat[0]
|
def get_response( fmt, *args )
|
||||||
when ?2 then SuccessCode
|
@socket.writeline sprintf(fmt, *args)
|
||||||
when ?3 then ContinueCode
|
recv_response()
|
||||||
when ?4 then ServerErrorCode
|
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
|
when ?5 then
|
||||||
case stat[1]
|
case res[1]
|
||||||
when ?0 then SyntaxErrorCode
|
when ?0 then ProtoSyntaxError
|
||||||
when ?3 then AuthErrorCode
|
when ?3 then ProtoAuthError
|
||||||
when ?5 then FatalErrorCode
|
when ?5 then ProtoFatalError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
klass ||= UnknownCode
|
raise etype, res if etype
|
||||||
|
res
|
||||||
Response.new(klass, stat, arr.join(''))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_reply
|
def critical
|
||||||
arr = []
|
return if @in_critical_block
|
||||||
while true
|
@in_critical_block = true
|
||||||
str = @socket.readline
|
result = yield()
|
||||||
break unless str[3] == ?- # "210-PIPELINING"
|
@in_critical_block = false
|
||||||
arr.push str
|
result
|
||||||
end
|
|
||||||
arr.push str
|
|
||||||
|
|
||||||
arr
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# for backward compatibility
|
|
||||||
module NetPrivate
|
|
||||||
SMTPCommand = ::Net::SMTPCommand
|
|
||||||
end
|
|
||||||
|
|
||||||
end # module Net
|
end # module Net
|
||||||
|
Loading…
x
Reference in New Issue
Block a user