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

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


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

View File

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

View File

@ -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

View File

@ -54,6 +54,7 @@ Replace 'pop3.server.address' your POP3 server address.
(3) close POP session by calling POP3#finish or use block form #start. (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

View File

@ -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

View File

@ -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