mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
* lib/net/protocol.rb: remove Protocol class.
* lib/net/smtp.rb (SMTP): ditto. * lib/net/pop.rb (POP3): ditto. * lib/net/http.rb (HTTP): ditto. * lib/net/protocol.rb: remove Command class. * lib/net/smtp.rb (SMTPCommand): ditto. * lib/net/pop.rb (POP3Command): ditto. * lib/net/pop.rb: remove APOPCommand class. * lib/net/protocol.rb: remove Code class and its all subclasses. * lib/net/protocol.rb: remove Response class and its all subclasses. * lib/net/pop.rb (POPMail): new method unique_id (alias uidl). git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@3747 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
fd5f913f33
commit
e3056c8803
5 changed files with 656 additions and 643 deletions
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>
|
||||
|
||||
* 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 HTTP < Protocol
|
||||
class HTTP
|
||||
|
||||
Revision = %q$Revision$.split[1]
|
||||
|
||||
HTTPVersion = '1.1'
|
||||
|
||||
|
||||
#
|
||||
# for backward compatibility
|
||||
#
|
||||
|
@ -591,7 +592,6 @@ module Net
|
|||
end
|
||||
private_class_method :setimplversion
|
||||
|
||||
|
||||
#
|
||||
# short cut methods
|
||||
#
|
||||
|
@ -644,13 +644,17 @@ module Net
|
|||
end
|
||||
private_class_method :get_by_uri
|
||||
|
||||
|
||||
#
|
||||
# connection
|
||||
# HTTP session management
|
||||
#
|
||||
|
||||
protocol_param :default_port, '80'
|
||||
protocol_param :socket_type, '::Net::InternetMessageIO'
|
||||
def HTTP.default_port
|
||||
80
|
||||
end
|
||||
|
||||
def HTTP.socket_type
|
||||
InternetMessageIO
|
||||
end
|
||||
|
||||
class << HTTP
|
||||
def start( address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil, &block )
|
||||
|
@ -666,25 +670,94 @@ module Net
|
|||
end
|
||||
end
|
||||
|
||||
def initialize( addr, port = nil )
|
||||
super
|
||||
def initialize( address, port = nil )
|
||||
@address = address
|
||||
@port = port || HTTP.default_port
|
||||
|
||||
@curr_http_version = HTTPVersion
|
||||
@seems_1_0_server = false
|
||||
@close_on_empty_response = false
|
||||
@socket = nil
|
||||
@started = false
|
||||
|
||||
@open_timeout = 30
|
||||
@read_timeout = 60
|
||||
|
||||
@debug_output = nil
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{@address}:#{@port} open=#{active?}>"
|
||||
end
|
||||
|
||||
def set_debug_output( arg ) # :nodoc:
|
||||
@debug_output = arg
|
||||
end
|
||||
|
||||
attr_reader :address
|
||||
attr_reader :port
|
||||
|
||||
attr_accessor :open_timeout
|
||||
|
||||
attr_reader :read_timeout
|
||||
|
||||
def read_timeout=( sec )
|
||||
@socket.read_timeout = sec if @socket
|
||||
@read_timeout = sec
|
||||
end
|
||||
|
||||
def started?
|
||||
@started
|
||||
end
|
||||
|
||||
alias active? started?
|
||||
|
||||
attr_accessor :close_on_empty_response
|
||||
|
||||
private
|
||||
def start
|
||||
raise IOError, 'HTTP session already opened' if @started
|
||||
if block_given?
|
||||
begin
|
||||
do_start
|
||||
return yield(self)
|
||||
ensure
|
||||
finish
|
||||
end
|
||||
end
|
||||
do_start
|
||||
self
|
||||
end
|
||||
|
||||
def do_start
|
||||
conn_socket
|
||||
@socket = self.class.socket_type.open(conn_address(), conn_port(),
|
||||
@open_timeout, @read_timeout,
|
||||
@debug_output)
|
||||
on_connect
|
||||
@started = true
|
||||
end
|
||||
private :do_start
|
||||
|
||||
def do_finish
|
||||
disconn_socket
|
||||
def conn_address
|
||||
address()
|
||||
end
|
||||
private :conn_address
|
||||
|
||||
def conn_port
|
||||
port()
|
||||
end
|
||||
private :conn_port
|
||||
|
||||
def on_connect
|
||||
end
|
||||
private :on_connect
|
||||
|
||||
def finish
|
||||
raise IOError, 'closing already closed HTTP session' unless @started
|
||||
@socket.close if @socket and not @socket.closed?
|
||||
@socket = nil
|
||||
@started = false
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# proxy
|
||||
|
@ -784,9 +857,8 @@ module Net
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# http operations
|
||||
# HTTP operations
|
||||
#
|
||||
|
||||
public
|
||||
|
@ -888,7 +960,8 @@ module Net
|
|||
|
||||
def begin_transport( req )
|
||||
if @socket.closed?
|
||||
reconn_socket
|
||||
@socket.reopen @open_timeout
|
||||
on_connect
|
||||
end
|
||||
if @seems_1_0_server
|
||||
req['connection'] = 'close'
|
||||
|
@ -930,7 +1003,6 @@ module Net
|
|||
false
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# utils
|
||||
#
|
||||
|
@ -954,7 +1026,7 @@ module Net
|
|||
|
||||
|
||||
###
|
||||
### header
|
||||
### Header
|
||||
###
|
||||
|
||||
module HTTPHeader
|
||||
|
@ -1012,6 +1084,7 @@ module Net
|
|||
def canonical( k )
|
||||
k.split(/-/).map {|i| i.capitalize }.join('-')
|
||||
end
|
||||
private :canonical
|
||||
|
||||
def range
|
||||
s = @header['range'] or return nil
|
||||
|
@ -1101,7 +1174,7 @@ module Net
|
|||
|
||||
|
||||
###
|
||||
### request
|
||||
### Request
|
||||
###
|
||||
|
||||
class HTTPGenericRequest
|
||||
|
@ -1188,14 +1261,12 @@ module Net
|
|||
|
||||
|
||||
class HTTPRequest < HTTPGenericRequest
|
||||
|
||||
def initialize( path, initheader = nil )
|
||||
super self.class::METHOD,
|
||||
self.class::REQUEST_HAS_BODY,
|
||||
self.class::RESPONSE_HAS_BODY,
|
||||
path, initheader
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
@ -1228,10 +1299,32 @@ module Net
|
|||
end
|
||||
|
||||
|
||||
###
|
||||
### Response
|
||||
###
|
||||
|
||||
module HTTPExceptions
|
||||
def initialize( msg, res )
|
||||
super msg
|
||||
@response = res
|
||||
end
|
||||
attr_reader :response
|
||||
alias data response
|
||||
end
|
||||
class HTTPError < ProtocolError
|
||||
include HTTPExceptions
|
||||
end
|
||||
class HTTPRetriableError < ProtoRetriableError
|
||||
include HTTPExceptions
|
||||
end
|
||||
# We cannot use the name "HTTPServerError", it is the name of the response.
|
||||
class HTTPServerException < ProtoServerError
|
||||
include HTTPExceptions
|
||||
end
|
||||
class HTTPFatalError < ProtoFatalError
|
||||
include HTTPExceptions
|
||||
end
|
||||
|
||||
###
|
||||
### response
|
||||
###
|
||||
|
||||
class HTTPResponse
|
||||
# predefine HTTPResponse class to allow inheritance
|
||||
|
@ -1245,30 +1338,29 @@ module Net
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
class HTTPUnknownResponse < HTTPResponse
|
||||
HAS_BODY = true
|
||||
EXCEPTION_TYPE = ProtocolError
|
||||
EXCEPTION_TYPE = HTTPError
|
||||
end
|
||||
class HTTPInformation < HTTPResponse # 1xx
|
||||
HAS_BODY = false
|
||||
EXCEPTION_TYPE = ProtocolError
|
||||
EXCEPTION_TYPE = HTTPError
|
||||
end
|
||||
class HTTPSuccess < HTTPResponse # 2xx
|
||||
HAS_BODY = true
|
||||
EXCEPTION_TYPE = ProtocolError
|
||||
EXCEPTION_TYPE = HTTPError
|
||||
end
|
||||
class HTTPRedirection < HTTPResponse # 3xx
|
||||
HAS_BODY = true
|
||||
EXCEPTION_TYPE = ProtoRetriableError
|
||||
EXCEPTION_TYPE = HTTPRetriableError
|
||||
end
|
||||
class HTTPClientError < HTTPResponse # 4xx
|
||||
HAS_BODY = true
|
||||
EXCEPTION_TYPE = ProtoServerError # for backward compatibility
|
||||
EXCEPTION_TYPE = HTTPServerException # for backward compatibility
|
||||
end
|
||||
class HTTPServerError < HTTPResponse # 5xx
|
||||
HAS_BODY = true
|
||||
EXCEPTION_TYPE = ProtoFatalError # for backward compatibility
|
||||
EXCEPTION_TYPE = HTTPFatalError # for backward compatibility
|
||||
end
|
||||
|
||||
class HTTPContinue < HTTPInformation # 100
|
||||
|
@ -1649,12 +1741,6 @@ module Net
|
|||
|
||||
# for backward compatibility
|
||||
|
||||
module NetPrivate
|
||||
HTTPResponse = ::Net::HTTPResponse
|
||||
HTTPGenericRequest = ::Net::HTTPGenericRequest
|
||||
HTTPRequest = ::Net::HTTPRequest
|
||||
HTTPHeader = ::Net::HTTPHeader
|
||||
end
|
||||
HTTPInformationCode = HTTPInformation
|
||||
HTTPSuccessCode = HTTPSuccess
|
||||
HTTPRedirectionCode = HTTPRedirection
|
||||
|
|
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.
|
||||
|
||||
This example is using block form #start to close the session.
|
||||
|
||||
=== Enshort Code
|
||||
|
||||
The example above is very verbose. You can enshort code by using
|
||||
|
@ -132,26 +133,48 @@ You can use utility method, Net::POP3.APOP(). Example:
|
|||
|
||||
require 'net/pop'
|
||||
|
||||
# use APOP authentication if $isapop == true
|
||||
# Use APOP authentication if $isapop == true
|
||||
pop = Net::POP3.APOP($isapop).new('apop.server.address', 110)
|
||||
pop.start(YourAccount', 'YourPassword') {|pop|
|
||||
# Rest code is same.
|
||||
}
|
||||
|
||||
=== Fetch Only Selected Mail Using POP UIDL Function
|
||||
|
||||
== Net::POP3 class
|
||||
If your POP server provides UIDL function,
|
||||
you can pop only selected mails from POP server.
|
||||
e.g.
|
||||
|
||||
def need_pop?( id )
|
||||
# determine if we need pop this mail...
|
||||
end
|
||||
|
||||
Net::POP3.start('pop.server', 110,
|
||||
'Your account', 'Your password') {|pop|
|
||||
pop.mails.select {|m| need_pop?(m.unique_id) }.each do |m|
|
||||
do_something(m.pop)
|
||||
end
|
||||
}
|
||||
|
||||
POPMail#unique_id method returns the unique-id of the message (String).
|
||||
Normally unique-id is a hash of the message.
|
||||
|
||||
|
||||
== class Net::POP3
|
||||
|
||||
=== Class Methods
|
||||
|
||||
: new( address, port = 110, apop = false )
|
||||
: new( address, port = 110, isapop = false )
|
||||
creates a new Net::POP3 object.
|
||||
This method does not open TCP connection yet.
|
||||
This method does NOT open TCP connection yet.
|
||||
|
||||
: start( address, port = 110, account, password )
|
||||
: start( address, port = 110, account, password ) {|pop| .... }
|
||||
equals to Net::POP3.new( address, port ).start( account, password )
|
||||
: start( address, port = 110, account, password, isapop = false )
|
||||
: start( address, port = 110, account, password, isapop = false ) {|pop| .... }
|
||||
equals to Net::POP3.new(address, port, isapop).start(account, password).
|
||||
This method raises POPAuthenticationError if authentication is failed.
|
||||
|
||||
Net::POP3.start( addr, port, account, password ) {|pop|
|
||||
# Typical usage
|
||||
Net::POP3.start(addr, port, account, password) {|pop|
|
||||
pop.each_mail do |m|
|
||||
file.write m.pop
|
||||
m.delete
|
||||
|
@ -163,17 +186,17 @@ You can use utility method, Net::POP3.APOP(). Example:
|
|||
returns Net::POP3 class object if false.
|
||||
Use this method like:
|
||||
|
||||
# example 1
|
||||
# Example 1
|
||||
pop = Net::POP3::APOP($isapop).new( addr, port )
|
||||
|
||||
# example 2
|
||||
# Example 2
|
||||
Net::POP3::APOP($isapop).start( addr, port ) {|pop|
|
||||
....
|
||||
}
|
||||
|
||||
: foreach( address, port = 110, account, password ) {|mail| .... }
|
||||
: foreach( address, port = 110, account, password, isapop = false ) {|mail| .... }
|
||||
starts POP3 protocol and iterates for each POPMail object.
|
||||
This method equals to
|
||||
This method equals to:
|
||||
|
||||
Net::POP3.start( address, port, account, password ) {|pop|
|
||||
pop.each_mail do |m|
|
||||
|
@ -181,29 +204,33 @@ You can use utility method, Net::POP3.APOP(). Example:
|
|||
end
|
||||
}
|
||||
|
||||
# example
|
||||
This method raises POPAuthenticationError if authentication is failed.
|
||||
|
||||
# Typical usage
|
||||
Net::POP3.foreach( 'your.pop.server', 110,
|
||||
'YourAccount', 'YourPassword' ) do |m|
|
||||
file.write m.pop
|
||||
m.delete if $DELETE
|
||||
end
|
||||
|
||||
: delete_all( address, port = 110, account, password )
|
||||
: delete_all( address, port = 110, account, password ) {|mail| .... }
|
||||
: delete_all( address, port = 110, account, password, isapop = false )
|
||||
: delete_all( address, port = 110, account, password, isapop = false ) {|mail| .... }
|
||||
starts POP3 session and delete all mails.
|
||||
If block is given, iterates for each POPMail object before delete.
|
||||
This method raises POPAuthenticationError if authentication is failed.
|
||||
|
||||
# example
|
||||
# Example
|
||||
Net::POP3.delete_all( addr, nil, 'YourAccount', 'YourPassword' ) do |m|
|
||||
m.pop file
|
||||
end
|
||||
|
||||
: auth_only( address, port = 110, account, password )
|
||||
: auth_only( address, port = 110, account, password, isapop = false )
|
||||
(just for POP-before-SMTP)
|
||||
opens POP3 session and does autholize and quit.
|
||||
This method must not be called while POP3 session is opened.
|
||||
This method raises POPAuthenticationError if authentication is failed.
|
||||
|
||||
# example
|
||||
# Example
|
||||
Net::POP3.auth_only( 'your.pop3.server',
|
||||
nil, # using default (110)
|
||||
'YourAccount',
|
||||
|
@ -218,7 +245,9 @@ You can use utility method, Net::POP3.APOP(). Example:
|
|||
When called with block, gives a POP3 object to block and
|
||||
closes the session after block call finish.
|
||||
|
||||
: active?
|
||||
This method raises POPAuthenticationError if authentication is failed.
|
||||
|
||||
: started?
|
||||
true if POP3 session is started.
|
||||
|
||||
: address
|
||||
|
@ -245,44 +274,62 @@ You can use utility method, Net::POP3.APOP(). Example:
|
|||
|
||||
: mails
|
||||
an array of Net::POPMail objects.
|
||||
This array is renewed when session started.
|
||||
This array is renewed when session restarts.
|
||||
|
||||
This method raises POPError if any problem happend.
|
||||
|
||||
: each_mail {|popmail| .... }
|
||||
: each {|popmail| .... }
|
||||
is equals to "pop3.mails.each"
|
||||
|
||||
This method raises POPError if any problem happend.
|
||||
|
||||
: delete_all
|
||||
: delete_all {|popmail| .... }
|
||||
deletes all mails on server.
|
||||
If called with block, gives mails to the block before deleting.
|
||||
|
||||
# example
|
||||
# Example
|
||||
n = 1
|
||||
pop.delete_all do |m|
|
||||
File.open("inbox/#{n}") {|f| f.write m.pop }
|
||||
n += 1
|
||||
end
|
||||
|
||||
This method raises POPError if any problem happend.
|
||||
|
||||
: auth_only( account, password )
|
||||
(just for POP-before-SMTP)
|
||||
opens POP3 session and does autholize and quit.
|
||||
This method must not be called while POP3 session is opened.
|
||||
# example
|
||||
pop = Net::POP3.new( 'your.pop3.server' )
|
||||
pop.auth_only 'YourAccount', 'YourPassword'
|
||||
|
||||
opens POP3 session, does authorization, then quit.
|
||||
You must not call this method after POP3 session is opened.
|
||||
|
||||
This method raises POPAuthenticationError if authentication is failed.
|
||||
|
||||
# Typical usage
|
||||
pop = Net::POP3.new('your.pop3.server')
|
||||
pop.auth_only('Your account', 'Your password')
|
||||
Net::SMTP.start(....) {|smtp|
|
||||
....
|
||||
}
|
||||
|
||||
: reset
|
||||
reset the session. All "deleted mark" are removed.
|
||||
|
||||
== Net::APOP
|
||||
This method raises POPError if any problem happend.
|
||||
|
||||
|
||||
== class Net::APOP
|
||||
|
||||
This class defines no new methods.
|
||||
Only difference from POP3 is using APOP authentification.
|
||||
|
||||
=== Super Class
|
||||
|
||||
Net::POP3
|
||||
|
||||
== Net::POPMail
|
||||
|
||||
== class Net::POPMail
|
||||
|
||||
A class of mail which exists on POP server.
|
||||
|
||||
|
@ -291,7 +338,9 @@ A class of mail which exists on POP server.
|
|||
: pop( dest = '' )
|
||||
This method fetches a mail and write to 'dest' using '<<' method.
|
||||
|
||||
# example
|
||||
This method raises POPError if any problem happend.
|
||||
|
||||
# Typical usage
|
||||
allmails = nil
|
||||
POP3.start( 'your.pop3.server', 110,
|
||||
'YourAccount, 'YourPassword' ) {|pop|
|
||||
|
@ -301,7 +350,9 @@ A class of mail which exists on POP server.
|
|||
: pop {|str| .... }
|
||||
gives the block part strings of a mail.
|
||||
|
||||
# example
|
||||
This method raises POPError if any problem happend.
|
||||
|
||||
# Typical usage
|
||||
POP3.start( 'localhost', 110 ) {|pop3|
|
||||
pop3.each_mail do |m|
|
||||
m.pop do |str|
|
||||
|
@ -311,20 +362,32 @@ A class of mail which exists on POP server.
|
|||
}
|
||||
|
||||
: header
|
||||
This method fetches only mail header.
|
||||
fetches only mail header.
|
||||
|
||||
This method raises POPError if any problem happend.
|
||||
|
||||
: top( lines )
|
||||
This method fetches mail header and LINES lines of body.
|
||||
fetches mail header and LINES lines of body.
|
||||
|
||||
This method raises POPError if any problem happend.
|
||||
|
||||
: delete
|
||||
deletes mail on server.
|
||||
|
||||
This method raises POPError if any problem happend.
|
||||
|
||||
: size
|
||||
mail size (bytes)
|
||||
|
||||
: deleted?
|
||||
true if mail was deleted
|
||||
|
||||
: unique_id
|
||||
returns an unique-id of the message.
|
||||
Normally unique-id is a hash of the message.
|
||||
|
||||
This method raises POPError if any problem happend.
|
||||
|
||||
=end
|
||||
|
||||
require 'net/protocol'
|
||||
|
@ -333,38 +396,55 @@ require 'digest/md5'
|
|||
|
||||
module Net
|
||||
|
||||
class BadResponseError < StandardError; end
|
||||
class POPError < ProtocolError; end
|
||||
class POPAuthenticationError < ProtoAuthError; end
|
||||
class POPBadResponse < StandardError; end
|
||||
|
||||
|
||||
class POP3 < Protocol
|
||||
class POP3
|
||||
|
||||
protocol_param :default_port, '110'
|
||||
protocol_param :command_type, '::Net::POP3Command'
|
||||
protocol_param :apop_command_type, '::Net::APOPCommand'
|
||||
protocol_param :mail_type, '::Net::POPMail'
|
||||
protocol_param :socket_type, '::Net::InternetMessageIO'
|
||||
Revision = %q$Revision$.split[1]
|
||||
|
||||
#
|
||||
# Class Parameters
|
||||
#
|
||||
|
||||
def POP3.default_port
|
||||
110
|
||||
end
|
||||
|
||||
def POP3.socket_type
|
||||
Net::InternetMessageIO
|
||||
end
|
||||
|
||||
#
|
||||
# Utilities
|
||||
#
|
||||
|
||||
def POP3.APOP( isapop )
|
||||
isapop ? APOP : POP3
|
||||
end
|
||||
|
||||
def POP3.foreach( address, port = nil,
|
||||
account = nil, password = nil, &block )
|
||||
start(address, port, account, password) {|pop|
|
||||
account = nil, password = nil,
|
||||
isapop = false, &block )
|
||||
start(address, port, account, password, isapop) {|pop|
|
||||
pop.each_mail(&block)
|
||||
}
|
||||
end
|
||||
|
||||
def POP3.delete_all( address, port = nil,
|
||||
account = nil, password = nil, &block )
|
||||
start(address, port, account, password) {|pop|
|
||||
account = nil, password = nil,
|
||||
isapop = false, &block )
|
||||
start(address, port, account, password, isapop) {|pop|
|
||||
pop.delete_all(&block)
|
||||
}
|
||||
end
|
||||
|
||||
def POP3.auth_only( address, port = nil,
|
||||
account = nil, password = nil )
|
||||
new(address, port).auth_only account, password
|
||||
account = nil, password = nil,
|
||||
isapop = false )
|
||||
new(address, port, isapop).auth_only account, password
|
||||
end
|
||||
|
||||
def auth_only( account, password )
|
||||
|
@ -375,39 +455,115 @@ module Net
|
|||
end
|
||||
|
||||
#
|
||||
# connection
|
||||
# Session management
|
||||
#
|
||||
|
||||
def initialize( addr, port = nil, apop = false )
|
||||
super addr, port
|
||||
@mails = nil
|
||||
@apop = false
|
||||
def POP3.start( address, port = nil,
|
||||
account = nil, password = nil,
|
||||
isapop = false, &block )
|
||||
new(address, port, isapop).start(account, password, &block)
|
||||
end
|
||||
|
||||
private
|
||||
def initialize( addr, port = nil, isapop = false )
|
||||
@address = addr
|
||||
@port = port || self.class.default_port
|
||||
@apop = isapop
|
||||
|
||||
@command = nil
|
||||
@socket = nil
|
||||
@started = false
|
||||
@open_timeout = 30
|
||||
@read_timeout = 60
|
||||
@debug_output = nil
|
||||
|
||||
@mails = nil
|
||||
@nmails = nil
|
||||
@bytes = nil
|
||||
end
|
||||
|
||||
def apop?
|
||||
@apop
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{@address}:#{@port} open=#{@started}>"
|
||||
end
|
||||
|
||||
def set_debug_output( arg ) # :nodoc:
|
||||
@debug_output = arg
|
||||
end
|
||||
|
||||
attr_reader :address
|
||||
attr_reader :port
|
||||
|
||||
attr_accessor :open_timeout
|
||||
attr_reader :read_timeout
|
||||
|
||||
def read_timeout=( sec )
|
||||
@command.socket.read_timeout = sec if @command
|
||||
@read_timeout = sec
|
||||
end
|
||||
|
||||
def started?
|
||||
@started
|
||||
end
|
||||
|
||||
alias active? started? # backward compatibility
|
||||
|
||||
def start( account, password )
|
||||
raise IOError, 'already closed POP session' if @started
|
||||
|
||||
if block_given?
|
||||
begin
|
||||
do_start account, password
|
||||
return yield(self)
|
||||
ensure
|
||||
finish unless @started
|
||||
end
|
||||
else
|
||||
do_start acount, password
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
def do_start( account, password )
|
||||
conn_socket
|
||||
conn_command
|
||||
@command.auth account, password
|
||||
@socket = self.class.socket_type.open(@address, @port,
|
||||
@open_timeout, @read_timeout, @debug_output)
|
||||
on_connect
|
||||
@command = POP3Command.new(@socket)
|
||||
if apop?
|
||||
@command.apop account, password
|
||||
else
|
||||
@command.auth account, password
|
||||
end
|
||||
@started = true
|
||||
end
|
||||
private :do_start
|
||||
|
||||
def conn_command
|
||||
@command = (@apop ? self.class.apop_command_type :
|
||||
self.class.command_type ).new(socket())
|
||||
def on_connect
|
||||
end
|
||||
private :on_connect
|
||||
|
||||
def do_finish
|
||||
def finish
|
||||
raise IOError, 'already closed POP session' unless @started
|
||||
@mails = nil
|
||||
disconn_command
|
||||
disconn_socket
|
||||
@command.quit if @command
|
||||
@command = nil
|
||||
@socket.close if @socket and not @socket.closed?
|
||||
@socket = nil
|
||||
@started = false
|
||||
end
|
||||
|
||||
#
|
||||
# POP operations
|
||||
#
|
||||
def command
|
||||
raise IOError, 'POP session not opened yet' \
|
||||
if not @socket or @socket.closed?
|
||||
@command
|
||||
end
|
||||
private :command
|
||||
|
||||
public
|
||||
#
|
||||
# POP protocol wrapper
|
||||
#
|
||||
|
||||
def mail_size
|
||||
return @nmails if @nmails
|
||||
|
@ -422,21 +578,17 @@ module Net
|
|||
end
|
||||
|
||||
def mails
|
||||
return @mails if @mails
|
||||
return @mails.dup if @mails
|
||||
if mail_size() == 0
|
||||
# some popd raises error for LIST on the empty mailbox.
|
||||
@mails = []
|
||||
return @mails
|
||||
return []
|
||||
end
|
||||
|
||||
mails = []
|
||||
mailclass = self.class.mail_type
|
||||
command().list.each_with_index do |size,idx|
|
||||
mails.push mailclass.new(idx, size, command()) if size
|
||||
end
|
||||
@mails = mails.freeze
|
||||
|
||||
@mails
|
||||
@mails = command().list.map {|num, size|
|
||||
POPMail.new(num, size, self, command())
|
||||
}
|
||||
@mails.dup
|
||||
end
|
||||
|
||||
def each_mail( &block )
|
||||
|
@ -461,25 +613,23 @@ module Net
|
|||
end
|
||||
end
|
||||
|
||||
def command
|
||||
io_check
|
||||
super
|
||||
end
|
||||
|
||||
def io_check
|
||||
raise IOError, 'POP session is not opened yet'\
|
||||
if not socket() or socket().closed?
|
||||
# internal use only (called from POPMail#uidl).
|
||||
def set_all_uids
|
||||
command().uidl.each do |num, uid|
|
||||
@mails.find {|m| m.number == num }.uid = uid
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# aliases
|
||||
POP = POP3
|
||||
POPSession = POP3
|
||||
POP3Session = POP3
|
||||
|
||||
class APOP < POP3
|
||||
def APOP.command_type
|
||||
APOPCommand
|
||||
def apop?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -488,171 +638,188 @@ module Net
|
|||
|
||||
class POPMail
|
||||
|
||||
def initialize( n, s, cmd )
|
||||
@num = n
|
||||
@size = s
|
||||
def initialize( num, size, pop, cmd )
|
||||
@number = num
|
||||
@size = size
|
||||
@pop = pop
|
||||
@command = cmd
|
||||
|
||||
@deleted = false
|
||||
@uid = nil
|
||||
end
|
||||
|
||||
attr_reader :number
|
||||
attr_reader :size
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{@num}#{@deleted ? ' deleted' : ''}>"
|
||||
"#<#{self.class} #{@number}#{@deleted ? ' deleted' : ''}>"
|
||||
end
|
||||
|
||||
def pop( dest = '', &block )
|
||||
dest = ReadAdapter.new(block) if block
|
||||
@command.retr @num, dest
|
||||
@command.retr(@number, (block ? ReadAdapter.new(block) : dest))
|
||||
end
|
||||
|
||||
alias all pop
|
||||
alias mail pop
|
||||
alias all pop # backward compatibility
|
||||
alias mail pop # backward compatibility
|
||||
|
||||
def top( lines, dest = '' )
|
||||
@command.top @num, lines, dest
|
||||
@command.top(@number, lines, dest)
|
||||
end
|
||||
|
||||
def header( dest = '' )
|
||||
top 0, dest
|
||||
top(0, dest)
|
||||
end
|
||||
|
||||
def delete
|
||||
@command.dele @num
|
||||
@command.dele @number
|
||||
@deleted = true
|
||||
end
|
||||
|
||||
alias delete! delete
|
||||
alias delete! delete # backward compatibility
|
||||
|
||||
def deleted?
|
||||
@deleted
|
||||
end
|
||||
|
||||
def uidl
|
||||
@command.uidl @num
|
||||
def unique_id
|
||||
return @uid if @uid
|
||||
@pop.set_all_uids
|
||||
@uid
|
||||
end
|
||||
|
||||
alias uidl unique_id
|
||||
|
||||
# internal use only (used from POP3#set_all_uids).
|
||||
def uid=( uid )
|
||||
@uid = uid
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class POP3Command < Command
|
||||
class POP3Command
|
||||
|
||||
def initialize( sock )
|
||||
super
|
||||
atomic {
|
||||
check_reply SuccessCode
|
||||
}
|
||||
@socket = sock
|
||||
@in_critical_block = false
|
||||
res = check_response(critical { recv_response() })
|
||||
@apop_stamp = res.slice(/<.+>/)
|
||||
end
|
||||
|
||||
def auth( account, pass )
|
||||
atomic {
|
||||
@socket.writeline 'USER ' + account
|
||||
check_reply_auth
|
||||
def inspect
|
||||
"#<#{self.class} socket=#{@socket}>"
|
||||
end
|
||||
|
||||
@socket.writeline 'PASS ' + pass
|
||||
check_reply_auth
|
||||
}
|
||||
def auth( account, password )
|
||||
check_response_auth(critical { get_response('USER ' + account) })
|
||||
check_response_auth(critical { get_response('PASS ' + password) })
|
||||
end
|
||||
|
||||
def apop( account, password )
|
||||
raise POPAuthenticationError.new('not APOP server; cannot login', nil)\
|
||||
unless @apop_stamp
|
||||
check_response_auth(critical {
|
||||
get_reply('APOP %s %s',
|
||||
account,
|
||||
Digest::MD5.hexdigest(@apop_stamp + password))
|
||||
})
|
||||
end
|
||||
|
||||
def list
|
||||
atomic {
|
||||
critical {
|
||||
getok 'LIST'
|
||||
list = []
|
||||
@socket.each_list_item do |line|
|
||||
m = /\A(\d+)[ \t]+(\d+)/.match(line) or
|
||||
raise BadResponse, "bad response: #{line}"
|
||||
list[m[1].to_i] = m[2].to_i
|
||||
raise POPBadResponse, "bad response: #{line}"
|
||||
list.push [m[1].to_i, m[2].to_i]
|
||||
end
|
||||
return list
|
||||
list
|
||||
}
|
||||
end
|
||||
|
||||
def stat
|
||||
atomic {
|
||||
@socket.writeline 'STAT'
|
||||
line = @socket.readline
|
||||
m = /\A\+OK (\d+)[ \t]+(\d+)/.match(line) or
|
||||
raise BadResponseError, "illegal response: #{line}"
|
||||
return [m[1].to_i, m[2].to_i]
|
||||
}
|
||||
res = check_response(critical { get_response('STAT') })
|
||||
m = /\A\+OK\s+(\d+)\s+(\d+)/.match(res) or
|
||||
raise POPBadResponse, "wrong response format: #{res}"
|
||||
[m[1].to_i, m[2].to_i]
|
||||
end
|
||||
|
||||
def rset
|
||||
atomic {
|
||||
getok 'RSET'
|
||||
}
|
||||
check_reply(critical { get_response 'RSET' })
|
||||
end
|
||||
|
||||
|
||||
def top( num, lines = 0, dest = '' )
|
||||
atomic {
|
||||
getok sprintf('TOP %d %d', num, lines)
|
||||
@socket.read_message_to dest
|
||||
critical {
|
||||
getok('TOP %d %d', num, lines)
|
||||
@socket.read_message_to(dest)
|
||||
}
|
||||
end
|
||||
|
||||
def retr( num, dest = '' )
|
||||
atomic {
|
||||
getok sprintf('RETR %d', num)
|
||||
critical {
|
||||
getok('RETR %d', num)
|
||||
@socket.read_message_to dest
|
||||
}
|
||||
end
|
||||
|
||||
def dele( num )
|
||||
atomic {
|
||||
getok sprintf('DELE %d', num)
|
||||
}
|
||||
check_response(critical { get_response('DELE %d', num) })
|
||||
end
|
||||
|
||||
def uidl( num )
|
||||
atomic {
|
||||
getok(sprintf('UIDL %d', num)).message.split(/ /)[1]
|
||||
}
|
||||
def uidl( num = nil )
|
||||
if num
|
||||
res = check_response(critical { get_response('UIDL %d', num) })
|
||||
res.split(/ /)[1]
|
||||
else
|
||||
critical {
|
||||
getok('UIDL')
|
||||
table = {}
|
||||
@socket.each_list_item do |line|
|
||||
num, uid = line.split
|
||||
table[num.to_i] = uid
|
||||
end
|
||||
table
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def quit
|
||||
atomic {
|
||||
getok 'QUIT'
|
||||
}
|
||||
check_response(critical { get_response('QUIT') })
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_reply_auth
|
||||
begin
|
||||
return check_reply(SuccessCode)
|
||||
rescue ProtocolError => err
|
||||
raise ProtoAuthError.new('Fail to POP authentication', err.response)
|
||||
end
|
||||
def getok( *reqs )
|
||||
@socket.writeline sprintf(*reqs)
|
||||
check_response(recv_response())
|
||||
end
|
||||
|
||||
def get_reply
|
||||
str = @socket.readline
|
||||
if /\A\+/ === str
|
||||
Response.new(SuccessCode, str[0,3], str[3, str.size - 3].strip)
|
||||
else
|
||||
Response.new(ErrorCode, str[0,4], str[4, str.size - 4].strip)
|
||||
end
|
||||
def get_response( *reqs )
|
||||
@socket.writeline sprintf(*reqs)
|
||||
recv_response()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class APOPCommand < POP3Command
|
||||
|
||||
def initialize( sock )
|
||||
@stamp = super(sock).message.slice(/<.+>/) or
|
||||
raise ProtoAuthError.new("not APOP server: cannot login", nil)
|
||||
def recv_response
|
||||
@socket.readline
|
||||
end
|
||||
|
||||
def auth( account, pass )
|
||||
atomic {
|
||||
@socket.writeline sprintf('APOP %s %s',
|
||||
account,
|
||||
Digest::MD5.hexdigest(@stamp + pass))
|
||||
check_reply_auth
|
||||
}
|
||||
def check_response( res )
|
||||
raise POPError, res unless /\A\+OK/i === res
|
||||
res
|
||||
end
|
||||
|
||||
def check_response_auth( res )
|
||||
raise POPAuthenticationError, res unless /\A\+OK/i === res
|
||||
res
|
||||
end
|
||||
|
||||
def critical
|
||||
return if @in_critical_block
|
||||
# Do not use ensure-block.
|
||||
@in_critical_block = true
|
||||
result = yield
|
||||
@in_critical_block = false
|
||||
result
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
266
lib/net/smtp.rb
266
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
|
||||
private :do_start
|
||||
|
||||
def do_finish
|
||||
disconn_command
|
||||
disconn_socket
|
||||
def finish
|
||||
raise IOError, 'closing already closed SMTP session' unless @started
|
||||
@command.quit if @command
|
||||
@command = nil
|
||||
@socket.close if @socket and not @socket.closed?
|
||||
@socket = nil
|
||||
@started = false
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# SMTP operations
|
||||
# SMTP wrapper
|
||||
#
|
||||
|
||||
public
|
||||
|
||||
def send_mail( mailsrc, from_addr, *to_addrs )
|
||||
do_ready from_addr, to_addrs.flatten
|
||||
command().write_mail mailsrc
|
||||
end
|
||||
|
||||
alias sendmail send_mail
|
||||
alias sendmail send_mail # backward compatibility
|
||||
|
||||
def ready( from_addr, *to_addrs, &block )
|
||||
do_ready from_addr, to_addrs.flatten
|
||||
|
@ -313,45 +380,49 @@ module Net
|
|||
command().rcpt to_addrs
|
||||
end
|
||||
|
||||
def command
|
||||
raise IOError, "closed session" unless @command
|
||||
@command
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
SMTPSession = SMTP
|
||||
|
||||
|
||||
class SMTPCommand < Command
|
||||
class SMTPCommand
|
||||
|
||||
def initialize( sock )
|
||||
super
|
||||
atomic {
|
||||
check_reply SuccessCode
|
||||
}
|
||||
@socket = sock
|
||||
@in_critical_block = false
|
||||
check_response(critical { recv_response() })
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} socket=#{@socket.inspect}>"
|
||||
end
|
||||
|
||||
def helo( domain )
|
||||
atomic {
|
||||
getok sprintf('HELO %s', domain)
|
||||
}
|
||||
getok('HELO %s', domain)
|
||||
end
|
||||
|
||||
def ehlo( domain )
|
||||
atomic {
|
||||
getok sprintf('EHLO %s', domain)
|
||||
}
|
||||
getok('EHLO %s', domain)
|
||||
end
|
||||
|
||||
# "PLAIN" authentication [RFC2554]
|
||||
def auth_plain( user, secret )
|
||||
atomic {
|
||||
getok sprintf('AUTH PLAIN %s',
|
||||
["\0#{user}\0#{secret}"].pack('m').chomp)
|
||||
}
|
||||
res = critical { get_response('AUTH PLAIN %s',
|
||||
["\0#{user}\0#{secret}"].pack('m').chomp) }
|
||||
raise SMTPAuthenticationError, res unless /\A2../ === res
|
||||
end
|
||||
|
||||
# "CRAM-MD5" authentication [RFC2195]
|
||||
def auth_cram_md5( user, secret )
|
||||
atomic {
|
||||
rep = getok('AUTH CRAM-MD5', ContinueCode)
|
||||
challenge = rep.msg.split(/ /)[1].unpack('m')[0]
|
||||
res = nil
|
||||
critical {
|
||||
res = check_response(get_response('AUTH CRAM-MD5'), true)
|
||||
challenge = res.split(/ /)[1].unpack('m')[0]
|
||||
secret = Digest::MD5.digest(secret) if secret.size > 64
|
||||
|
||||
isecret = secret + "\0" * (64 - secret.size)
|
||||
|
@ -363,86 +434,89 @@ module Net
|
|||
tmp = Digest::MD5.digest(isecret + challenge)
|
||||
tmp = Digest::MD5.hexdigest(osecret + tmp)
|
||||
|
||||
getok [user + ' ' + tmp].pack('m').gsub(/\s+/, '')
|
||||
res = get_response([user + ' ' + tmp].pack('m').gsub(/\s+/, ''))
|
||||
}
|
||||
raise SMTPAuthenticationError, res unless /\A2../ === res
|
||||
end
|
||||
|
||||
def mailfrom( fromaddr )
|
||||
atomic {
|
||||
getok sprintf('MAIL FROM:<%s>', fromaddr)
|
||||
}
|
||||
getok('MAIL FROM:<%s>', fromaddr)
|
||||
end
|
||||
|
||||
def rcpt( toaddrs )
|
||||
toaddrs.each do |i|
|
||||
atomic {
|
||||
getok sprintf('RCPT TO:<%s>', i)
|
||||
}
|
||||
getok('RCPT TO:<%s>', i)
|
||||
end
|
||||
end
|
||||
|
||||
def write_mail( src )
|
||||
atomic {
|
||||
getok 'DATA', ContinueCode
|
||||
res = critical {
|
||||
check_response(get_response('DATA'), true)
|
||||
@socket.write_message src
|
||||
check_reply SuccessCode
|
||||
recv_response()
|
||||
}
|
||||
check_response(res)
|
||||
end
|
||||
|
||||
def through_mail( &block )
|
||||
atomic {
|
||||
getok 'DATA', ContinueCode
|
||||
res = critical {
|
||||
check_response(get_response('DATA'), true)
|
||||
@socket.through_message(&block)
|
||||
check_reply SuccessCode
|
||||
recv_response()
|
||||
}
|
||||
check_response(res)
|
||||
end
|
||||
|
||||
def quit
|
||||
atomic {
|
||||
getok 'QUIT'
|
||||
}
|
||||
getok('QUIT')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_reply
|
||||
arr = read_reply
|
||||
stat = arr[0][0,3]
|
||||
def getok( fmt, *args )
|
||||
@socket.writeline sprintf(fmt, *args)
|
||||
check_response(critical { recv_response() })
|
||||
end
|
||||
|
||||
klass = case stat[0]
|
||||
when ?2 then SuccessCode
|
||||
when ?3 then ContinueCode
|
||||
when ?4 then ServerErrorCode
|
||||
def get_response( fmt, *args )
|
||||
@socket.writeline sprintf(fmt, *args)
|
||||
recv_response()
|
||||
end
|
||||
|
||||
def recv_response
|
||||
res = ''
|
||||
while true
|
||||
line = @socket.readline
|
||||
res << line << "\n"
|
||||
break unless line[3] == ?- # "210-PIPELINING"
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
def check_response( res, cont = false )
|
||||
etype = case res[0]
|
||||
when ?2 then nil
|
||||
when ?3 then cont ? nil : ProtoUnknownError
|
||||
when ?4 then ProtoServerError
|
||||
when ?5 then
|
||||
case stat[1]
|
||||
when ?0 then SyntaxErrorCode
|
||||
when ?3 then AuthErrorCode
|
||||
when ?5 then FatalErrorCode
|
||||
case res[1]
|
||||
when ?0 then ProtoSyntaxError
|
||||
when ?3 then ProtoAuthError
|
||||
when ?5 then ProtoFatalError
|
||||
end
|
||||
end
|
||||
klass ||= UnknownCode
|
||||
|
||||
Response.new(klass, stat, arr.join(''))
|
||||
raise etype, res if etype
|
||||
res
|
||||
end
|
||||
|
||||
def read_reply
|
||||
arr = []
|
||||
while true
|
||||
str = @socket.readline
|
||||
break unless str[3] == ?- # "210-PIPELINING"
|
||||
arr.push str
|
||||
end
|
||||
arr.push str
|
||||
|
||||
arr
|
||||
def critical
|
||||
return if @in_critical_block
|
||||
@in_critical_block = true
|
||||
result = yield()
|
||||
@in_critical_block = false
|
||||
result
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
# for backward compatibility
|
||||
module NetPrivate
|
||||
SMTPCommand = ::Net::SMTPCommand
|
||||
end
|
||||
|
||||
end # module Net
|
||||
|
|
Loading…
Reference in a new issue