summaryrefslogtreecommitdiff
path: root/lib/net
diff options
context:
space:
mode:
authoraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2001-12-30 19:18:45 +0000
committeraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2001-12-30 19:18:45 +0000
commitf3d9a0cc213f8b09155f063e057afb576178557e (patch)
tree92cf1665627f99c3fccdb3cf806f809348b3bd5c /lib/net
parent653f326bb108108b82890e37400b4ca6e851e7ed (diff)
aamine
* lib/net/protocol.rb: Protocol#start returns the return value of block. * lib/net/protocol.rb: set timeout limit by default. * lib/net/protocol.rb: new methods WriteAdapter#write, puts, print, printf. * lib/net/http.rb: rename HTTP#get2 to request_get, post2 to request_post ... * lib/net/smtp.rb: should not resolve HELO domain automatically. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@1951 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/net')
-rw-r--r--lib/net/http.rb315
-rw-r--r--lib/net/pop.rb178
-rw-r--r--lib/net/protocol.rb414
-rw-r--r--lib/net/smtp.rb196
4 files changed, 565 insertions, 538 deletions
diff --git a/lib/net/http.rb b/lib/net/http.rb
index 3925799005..f2a259a18a 100644
--- a/lib/net/http.rb
+++ b/lib/net/http.rb
@@ -215,21 +215,22 @@ Yes, this is not thread-safe.
: proxy_port
port number of proxy host. If self does not use a proxy, nil.
-: get( path, header = nil, dest = '' )
+: get( path, header = nil )
: get( path, header = nil ) {|str| .... }
gets data from PATH on the connecting host.
HEADER must be a Hash like { 'Accept' => '*/*', ... }.
- Response body is written into DEST by using "<<" method.
- This method returns Net::HTTPResponse object.
+ In version 1.1, this method returns a pair of objects,
+ a Net::HTTPResponse object and entity body string.
+ In version 1.2, this method returns a Net::HTTPResponse
+ object.
- If called with block, gives entity body little by little
- to the block (as String).
+ If called with block, gives entity body string to the block
+ little by little.
In version 1.1, this method might raises exception for also
3xx (redirect). On the case you can get a HTTPResponse object
by "anException.response".
-
In version 1.2, this method never raises exception.
# version 1.1 (bundled with Ruby 1.6)
@@ -248,10 +249,6 @@ Yes, this is not thread-safe.
f.write str
end
}
- # same effect
- File.open( 'save.txt', 'w' ) {|f|
- http.get '/~foo/', nil, f
- }
: head( path, header = nil )
gets only header from PATH on the connecting host.
@@ -262,6 +259,7 @@ Yes, this is not thread-safe.
In version 1.1, this method might raises exception for also
3xx (redirect). On the case you can get a HTTPResponse object
by "anException.response".
+ In version 1.2, this method never raises exception.
response = nil
Net::HTTP.start( 'some.www.server', 80 ) {|http|
@@ -269,67 +267,70 @@ Yes, this is not thread-safe.
}
p response['content-type']
-: post( path, data, header = nil, dest = '' )
+: post( path, data, header = nil )
: post( path, data, header = nil ) {|str| .... }
- posts "data" (must be String) to "path".
- If the body exists, also gets entity body.
- Response body is written into "dest" by using "<<" method.
- "header" must be a Hash like { 'Accept' => '*/*', ... }.
- This method returns Net::HTTPResponse object.
+ posts DATA (must be String) to PATH. HEADER must be a Hash
+ like { 'Accept' => '*/*', ... }.
+
+ In version 1.1, this method returns a pair of objects, a
+ Net::HTTPResponse object and an entity body string.
+ In version 1.2, this method returns a Net::HTTPReponse object.
If called with block, gives a part of entity body string.
In version 1.1, this method might raises exception for also
3xx (redirect). On the case you can get a HTTPResponse object
by "anException.response".
+ In version 1.2, this method never raises exception.
# version 1.1
- response, body = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' )
+ response, body = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' )
+
# version 1.2
- response = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' )
- # compatible for both version
- response , = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' )
+ response = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' )
+
+ # compatible in both version
+ response , = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' )
# using block
File.open( 'save.html', 'w' ) {|f|
- http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' ) do |str|
+ http.post( '/cgi-bin/search.rb',
+ 'query=subject&target=ruby' ) do |str|
f.write str
end
}
- # same effect
- File.open( 'save.html', 'w' ) {|f|
- http.post '/cgi-bin/search.rb', 'querytype=subject&target=ruby', nil, f
- }
-: get2( path, header = nil )
-: get2( path, header = nil ) {|response| .... }
+: request_get( path, header = nil )
+: request_get( path, header = nil ) {|response| .... }
gets entity from PATH. This method returns a HTTPResponse object.
When called with block, keep connection while block is executed
and gives a HTTPResponse object to the block.
- This method never raise any ProtocolErrors.
+ This method never raises Net::* exceptions.
# example
- response = http.get2( '/index.html' )
+ response = http.request_get( '/index.html' )
p response['content-type']
puts response.body # body is already read
# using block
- http.get2( '/index.html' ) {|response|
+ http.request_get( '/index.html' ) {|response|
p response['content-type']
response.read_body do |str| # read body now
print str
end
}
-: post2( path, header = nil )
-: post2( path, header = nil ) {|response| .... }
+: request_post( path, data, header = nil )
+: request_post( path, data, header = nil ) {|response| .... }
posts data to PATH. This method returns a HTTPResponse object.
When called with block, gives a HTTPResponse object to the block
before reading entity body, with keeping connection.
+ This method never raises Net::* exceptions.
+
# example
response = http.post2( '/cgi-bin/nice.rb', 'datadatadata...' )
p response.status
@@ -346,12 +347,14 @@ Yes, this is not thread-safe.
: request( request [, data] )
: request( request [, data] ) {|response| .... }
- sends a HTTPRequest object REQUEST to the (remote) http server.
+ sends a HTTPRequest object REQUEST to the HTTP server.
This method also writes DATA string if REQUEST is a post/put request.
Giving DATA for get/head request causes ArgumentError.
- If called with block, passes a HTTPResponse object to the block
- before reading entity body.
+ If called with block, this method passes a HTTPResponse object to
+ the block, without reading entity body.
+
+ This method never raises Net::* exceptions.
== class Net::HTTP::Get, Head, Post
@@ -460,17 +463,39 @@ module Net
def initialize( addr, port = nil )
super
-
@curr_http_version = HTTPVersion
@seems_1_0_server = false
end
private
- def conn_command( sock )
+ def do_start
+ conn_socket
end
def do_finish
+ disconn_socket
+ end
+
+
+ #
+ # short cut methods
+ #
+
+ def HTTP.get( addr, path, port = nil )
+ req = Get.new( path )
+ resp = nil
+ new( addr, port || HTTP.port ).start {|http|
+ resp = http.request( req )
+ }
+ resp.body
+ end
+
+ def HTTP.get_print( addr, path, port = nil )
+ new( addr, port || HTTP.port ).start {|http|
+ http.get path, nil, $stdout
+ }
+ nil
end
@@ -480,7 +505,6 @@ module Net
public
-
class << self
def Proxy( p_addr, p_port = nil )
@@ -496,7 +520,7 @@ module Net
def new( address, port = nil, p_addr = nil, p_port = nil )
c = p_addr ? self::Proxy(p_addr, p_port) : self
i = c.orig_new( address, port )
- setvar i
+ setimplversion i
i
end
@@ -543,22 +567,26 @@ module Net
mod = self
klass = Class.new( HTTP )
klass.module_eval {
- include mod
- @is_proxy_class = true
- @proxy_address = p_addr
- @proxy_port = p_port
+ include mod
+ @is_proxy_class = true
+ @proxy_address = p_addr
+ @proxy_port = p_port
}
klass
end
private
- def conn_socket( addr, port )
- super proxy_address, proxy_port
+ def conn_address
+ proxy_address()
+ end
+
+ def conn_port
+ proxy_port()
end
def edit_path( path )
- 'http://' + addr_port + path
+ 'http://' + addr_port() + path
end
end # module ProxyMod
@@ -590,7 +618,7 @@ module Net
private
- def setvar( obj )
+ def setimplversion( obj )
f = @@newimpl
obj.instance_eval { @newimpl = f }
end
@@ -604,71 +632,92 @@ module Net
public
- def self.define_http_method_interface( nm, hasdest, hasdata )
- name = nm.id2name.downcase
- cname = nm.id2name
- lineno = __LINE__ + 2
- src = <<" ----"
-
- def #{name}( path, #{hasdata ? 'data,' : ''}
- u_header = nil #{hasdest ? ',dest = nil, &block' : ''} )
- resp = nil
- request(
- #{cname}.new( path, u_header ) #{hasdata ? ',data' : ''}
- ) do |resp|
- resp.read_body( #{hasdest ? 'dest, &block' : ''} )
- end
- if @newimpl then
- resp
- else
- resp.value
- #{hasdest ? 'return resp, resp.body' : 'resp'}
- end
- end
+ def get( path, initheader = nil, dest = nil, &block )
+ res = nil
+ request( Get.new(path,initheader) ) {|res|
+ res.read_body dest, &block
+ }
+ unless @newimpl then
+ res.value
+ return res, res.body
+ end
- def #{name}2( path, #{hasdata ? 'data,' : ''}
- u_header = nil, &block )
- request( #{cname}.new(path, u_header),
- #{hasdata ? 'data,' : ''} &block )
- end
- ----
- module_eval src, __FILE__, lineno
+ res
+ end
+
+ def head( path, initheader = nil )
+ res = request( Head.new(path,initheader) )
+ @newimpl or res.value
+ res
+ end
+
+ def post( path, data, initheader = nil, dest = nil, &block )
+ res = nil
+ request( Post.new(path,initheader), data ) {|res|
+ res.read_body dest, &block
+ }
+ unless @newimpl then
+ res.value
+ return res, res.body
+ end
+
+ res
end
- define_http_method_interface :Get, true, false
- define_http_method_interface :Head, false, false
- define_http_method_interface :Post, true, true
- define_http_method_interface :Put, false, true
+ def put( path, data, initheader = nil )
+ res = request( Put.new(path,initheader), data )
+ @newimpl or res.value
+ res
+ end
+
+ def request_get( path, initheader = nil, &block )
+ request Get.new(path,initheader), &block
+ end
+
+ def request_head( path, initheader = nil, &block )
+ request Head.new(path,initheader), &block
+ end
+
+ def request_post( path, data, initheader = nil, &block )
+ request Post.new(path,initheader), data, &block
+ end
+
+ def request_put( path, data, initheader = nil, &block )
+ request Put.new(path,initheader), data, &block
+ end
+
+ alias get2 request_get
+ alias head2 request_head
+ alias post2 request_post
+ alias put2 request_put
+
+ def send_request( name, path, body = nil, header = nil )
+ r = HTTPGenericRequest.new( name, (body ? true : false), true,
+ path, header )
+ request r, body
+ end
def request( req, body = nil, &block )
unless active? then
start {
- req['connection'] = 'close'
- return request(req, body, &block)
+ req['connection'] = 'close'
+ return request(req, body, &block)
}
end
connecting( req ) {
- req.__send__( :exec,
- @socket, @curr_http_version, edit_path(req.path), body )
- yield req.response if block_given?
+ req.__send__( :exec,
+ @socket, @curr_http_version, edit_path(req.path), body )
+ yield req.response if block_given?
}
req.response
end
- def send_request( name, path, body = nil, header = nil )
- r = HTTPGenericRequest.new( name, (body ? true : false), true,
- path, header )
- request r, body
- end
-
-
private
-
def connecting( req )
if @socket.closed? then
- re_connect
+ reconn_socket
end
if not req.body_exist? or @seems_1_0_server then
req['connection'] = 'close'
@@ -712,25 +761,6 @@ module Net
# utils
#
- public
-
- def self.get( addr, path, port = nil )
- req = Get.new( path )
- resp = nil
- new( addr, port || HTTP.port ).start {|http|
- resp = http.request( req )
- }
- resp.body
- end
-
- def self.get_print( addr, path, port = nil )
- new( addr, port || HTTP.port ).start {|http|
- http.get path, nil, $stdout
- }
- nil
- end
-
-
private
def addr_port
@@ -746,8 +776,6 @@ module Net
end
- HTTPSession = HTTP
-
class Code
@@ -894,9 +922,7 @@ module Net
end
def range=( r, fin = nil )
- if fin then
- r = r ... r+fin
- end
+ r = (r ... r + fin) if fin
case r
when Numeric
@@ -970,7 +996,7 @@ module Net
include HTTPHeader
- def initialize( m, reqbody, resbody, path, uhead = nil )
+ def initialize( m, reqbody, resbody, path, initheader = nil )
@method = m
@request_has_body = reqbody
@response_has_body = resbody
@@ -978,8 +1004,8 @@ module Net
@response = nil
@header = tmp = {}
- return unless uhead
- uhead.each do |k,v|
+ return unless initheader
+ initheader.each do |k,v|
key = k.downcase
if tmp.key? key then
$stderr.puts "WARNING: duplicated HTTP header: #{k}" if $VERBOSE
@@ -1023,7 +1049,7 @@ module Net
check_arg_n body
sendreq_no_body sock, ver, path
end
- @response = r = get_response( sock )
+ @response = r = get_response(sock)
r
end
@@ -1033,12 +1059,8 @@ module Net
end
def check_arg_b( data, block )
- if data and block then
- raise ArgumentError, 'both of data and block given'
- end
- unless data or block then
- raise ArgumentError, 'str or block required'
- end
+ (data and block) and raise ArgumentError, 'both of data and block given'
+ (data or block) or raise ArgumentError, 'str or block required'
end
def check_arg_n( data )
@@ -1094,11 +1116,11 @@ module Net
class HTTPRequest < HTTPGenericRequest
- def initialize( path, uhead = nil )
+ def initialize( path, initheader = nil )
super type::METHOD,
type::REQUEST_HAS_BODY,
type::RESPONSE_HAS_BODY,
- path, uhead
+ path, initheader
end
end
@@ -1226,7 +1248,7 @@ module Net
while true do
line = sock.readuntil( "\n", true ) # ignore EOF
- line.sub!( /\s+\z/, '' ) # don't use chop!
+ line.sub!( /\s+\z/, '' ) # don't use chop!
break if line.empty?
m = /\A([^:]+):\s*/.match( line )
@@ -1298,22 +1320,22 @@ module Net
#
def read_body( dest = nil, &block )
- if @read and (dest or block) then
- raise IOError, "#{type}\#read_body called twice with argument"
+ if @read then
+ (dest or block) and
+ raise IOError, "#{type}\#read_body called twice with argument"
+ return @body
end
- unless @read then
- to = procdest( dest, block )
- stream_check
+ to = procdest(dest, block)
+ stream_check
- if @body_exist and code_type.body_exist? then
- read_body_0 to
- @body = to
- else
- @body = nil
- end
- @read = true
+ if @body_exist and code_type.body_exist? then
+ read_body_0 to
+ @body = to
+ else
+ @body = nil
end
+ @read = true
@body
end
@@ -1321,10 +1343,8 @@ module Net
alias body read_body
alias entity read_body
-
private
-
def terminate
read_body
end
@@ -1353,11 +1373,11 @@ module Net
while true do
line = @socket.readline
- m = /[0-9a-fA-F]+/.match( line )
+ m = /[0-9a-fA-F]+/.match(line)
m or raise HTTPBadResponse, "wrong chunk size line: #{line}"
len = m[0].hex
break if len == 0
- @socket.read( len, dest ); total += len
+ @socket.read len, dest; total += len
@socket.read 2 # \r\n
end
until @socket.readline.empty? do
@@ -1384,6 +1404,9 @@ module Net
# for backward compatibility
+
+ HTTPSession = HTTP
+
module NetPrivate
HTTPResponse = ::Net::HTTPResponse
HTTPGenericRequest = ::Net::HTTPGenericRequest
diff --git a/lib/net/pop.rb b/lib/net/pop.rb
index 02729ca259..1dbbc9aeb0 100644
--- a/lib/net/pop.rb
+++ b/lib/net/pop.rb
@@ -367,86 +367,96 @@ module Net
end
+ def auth_only( account, password )
+ active? and raise IOError, 'opening already opened POP session'
+ start( account, password ) {
+ ;
+ }
+ end
+
+
+ #
+ # connection
+ #
+
def initialize( addr, port = nil, apop = false )
super addr, port
@mails = nil
@apop = false
end
- def auth_only( account, password )
- begin
- connect
- @active = true
- @command.auth address(), port()
- @command.quit
- ensure
- @active = false
- disconnect
- end
+ private
+
+ def do_start( account, password )
+ conn_socket
+ @command = (@apop ? type.apop_command_type : type.command_type).new(socket())
+ @command.auth account, password
+ end
+
+ def do_finish
+ @mails = nil
+ disconn_command
+ disconn_socket
end
- attr :mails
+
+ #
+ # POP operations
+ #
+
+ public
+
+ def mails
+ return @mails if @mails
+
+ mails = []
+ mtype = type.mail_type
+ command().list.each_with_index do |size,idx|
+ mails.push mtype.new(idx, size, command()) if size
+ end
+ @mails = mails.freeze
+ end
def each_mail( &block )
- io_check
- @mails.each( &block )
+ mails().each( &block )
end
alias each each_mail
def delete_all
- io_check
- @mails.each do |m|
+ mails().each do |m|
yield m if block_given?
m.delete unless m.deleted?
end
end
def reset
- io_check
- @command.rset
- @mails.each do |m|
+ command().rset
+ mails().each do |m|
m.instance_eval { @deleted = false }
end
end
- private
-
- def conn_command( sock )
- @command =
- (@apop ? type.apop_command_type : type.command_type).new(sock)
- end
-
- def do_start( account, password )
- @command.auth account, password
-
- mails = []
- mtype = type.mail_type
- @command.list.each_with_index do |size,idx|
- mails.push mtype.new(idx, size, @command) if size
- end
- @mails = mails.freeze
+ def command
+ io_check
+ super
end
def io_check
- (not @socket or @socket.closed?) and
- raise IOError, 'pop session is not opened yet'
+ (not socket() or socket().closed?) and
+ raise IOError, 'POP session is not opened yet'
end
end
- POP = POP3
- POPSession = POP3
- POP3Session = POP3
+ POP = POP3
class APOP < POP3
protocol_param :command_type, '::Net::APOPCommand'
end
- APOPSession = APOP
-
class POPMail
@@ -500,86 +510,84 @@ module Net
end
-
class POP3Command < Command
def initialize( sock )
super
- critical {
- check_reply SuccessCode
+ atomic {
+ check_reply SuccessCode
}
end
def auth( account, pass )
- critical {
- @socket.writeline 'USER ' + account
- check_reply_auth
+ atomic {
+ @socket.writeline 'USER ' + account
+ check_reply_auth
- @socket.writeline 'PASS ' + pass
- check_reply_auth
+ @socket.writeline 'PASS ' + pass
+ check_reply_auth
}
end
def list
arr = []
- critical {
- getok 'LIST'
- @socket.read_pendlist do |line|
- m = /\A(\d+)[ \t]+(\d+)/.match(line) or
- raise BadResponse, "illegal response: #{line}"
- arr[ m[1].to_i ] = m[2].to_i
- end
+ atomic {
+ getok 'LIST'
+ @socket.read_pendlist do |line|
+ m = /\A(\d+)[ \t]+(\d+)/.match(line) or
+ raise BadResponse, "illegal response: #{line}"
+ arr[ m[1].to_i ] = m[2].to_i
+ end
}
arr
end
def rset
- critical {
- getok 'RSET'
+ atomic {
+ getok 'RSET'
}
end
def top( num, lines = 0, dest = '' )
- critical {
- getok sprintf( 'TOP %d %d', num, lines )
- @socket.read_pendstr dest
+ atomic {
+ getok sprintf( 'TOP %d %d', num, lines )
+ @socket.read_pendstr dest
}
end
def retr( num, dest = '', &block )
- critical {
- getok sprintf('RETR %d', num)
- @socket.read_pendstr dest, &block
+ atomic {
+ getok sprintf('RETR %d', num)
+ @socket.read_pendstr dest, &block
}
end
def dele( num )
- critical {
- getok sprintf('DELE %d', num)
+ atomic {
+ getok sprintf('DELE %d', num)
}
end
def uidl( num )
- critical {
- getok( sprintf('UIDL %d', num) ).msg.split(' ')[1]
+ atomic {
+ getok( sprintf('UIDL %d', num) ).msg.split(' ')[1]
}
end
def quit
- critical {
- getok 'QUIT'
+ atomic {
+ getok 'QUIT'
}
end
-
private
def check_reply_auth
begin
- return check_reply( SuccessCode )
+ return check_reply(SuccessCode)
rescue ProtocolError => err
- raise ProtoAuthError.new( 'Fail to POP authentication', err.response )
+ raise ProtoAuthError.new('Fail to POP authentication', err.response)
end
end
@@ -599,22 +607,28 @@ module Net
class APOPCommand < POP3Command
def initialize( sock )
- rep = super( sock )
-
- m = /<.+>/.match( rep.msg ) or
- raise ProtoAuthError.new( "not APOP server: cannot login", nil )
+ response = super(sock)
+ m = /<.+>/.match(response.msg) or
+ raise ProtoAuthError.new("not APOP server: cannot login", nil)
@stamp = m[0]
end
def auth( account, pass )
- critical {
- @socket.writeline sprintf( 'APOP %s %s',
- account,
- Digest::MD5.hexdigest(@stamp + pass) )
- check_reply_auth
+ atomic {
+ @socket.writeline sprintf('APOP %s %s',
+ account,
+ Digest::MD5.hexdigest(@stamp + pass))
+ check_reply_auth
}
end
end
+
+ # for backward compatibility
+
+ POPSession = POP3
+ POP3Session = POP3
+ APOPSession = APOP
+
end # module Net
diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb
index 4f95434f99..f21bdf72f1 100644
--- a/lib/net/protocol.rb
+++ b/lib/net/protocol.rb
@@ -28,27 +28,17 @@ module Net
Version = '1.2.3'
Revision = %q$Revision$.split(/\s+/)[1]
- class << self
-
- def start( address, port = nil, *args )
- instance = new( address, port )
- if block_given? then
- instance.start( *args ) { yield instance }
- else
- instance.start( *args )
- instance
- end
- end
+ class << self
private
def protocol_param( name, val )
- module_eval %-
- def self.#{name.id2name}
- #{val}
- end
- -
+ module_eval <<-End, __FILE__, __LINE__ + 1
+ def self.#{name.id2name}
+ #{val}
+ end
+ End
end
end
@@ -61,11 +51,11 @@ module Net
# protocol_param command_type
# protocol_param socket_type (optional)
#
- # private method do_start (optional)
- # private method do_finish (optional)
+ # private method do_start
+ # private method do_finish
#
- # private method on_connect (optional)
- # private method on_disconnect (optional)
+ # private method conn_address
+ # private method conn_port
#
protocol_param :port, 'nil'
@@ -73,6 +63,19 @@ module Net
protocol_param :socket_type, '::Net::BufferedSocket'
+ def Protocol.start( address, port = nil, *args )
+ instance = new( address, port )
+
+ if block_given? then
+ ret = nil
+ instance.start( *args ) { ret = yield(instance) }
+ ret
+ else
+ instance.start( *args )
+ instance
+ end
+ end
+
def initialize( addr, port = nil )
@address = addr
@port = port || type.port
@@ -82,8 +85,8 @@ module Net
@active = false
- @open_timeout = nil
- @read_timeout = nil
+ @open_timeout = 30
+ @read_timeout = 60
@dout = nil
end
@@ -112,90 +115,80 @@ module Net
end
#
- # open session
+ # open
#
def start( *args )
- active? and raise IOError, 'protocol has been opened already'
+ @active and raise IOError, 'protocol has been opened already'
if block_given? then
begin
- _start args
- yield self
+ do_start( *args )
+ @active = true
+ return yield(self)
ensure
- finish if active?
+ finish if @active
end
- else
- _start args
end
+
+ do_start( *args )
+ @active = true
nil
end
private
- def _start( args )
- connect
- do_start( *args )
- @active = true
- end
+ # abstract do_start()
- def connect
- conn_socket @address, @port
+ def conn_socket
+ @socket = type.socket_type.open(
+ conn_address(), conn_port(),
+ @open_timeout, @read_timeout, @dout )
on_connect
- conn_command @socket
end
- def re_connect
+ alias conn_address address
+ alias conn_port port
+
+ def reconn_socket
@socket.reopen @open_timeout
on_connect
end
- def conn_socket( addr, port )
- @socket = type.socket_type.open(
- addr, port, @open_timeout, @read_timeout, @dout )
- end
-
- def conn_command( sock )
- @command = type.command_type.new( sock )
+ def conn_command
+ @command = type.command_type.new(@socket)
end
def on_connect
end
- def do_start
- end
-
#
- # close session
+ # close
#
public
def finish
- active? or raise IOError, 'already closed protocol'
-
- do_finish if @command and not @command.critical?
- disconnect
+ active? or raise IOError, 'closing already closed protocol'
+ do_finish
@active = false
nil
end
private
- def do_finish
- @command.quit
- end
+ # abstract do_finish()
- def disconnect
+ def disconn_command
+ @command.quit if @command and not @command.critical?
@command = nil
+ end
+
+ def disconn_socket
if @socket and not @socket.closed? then
@socket.close
end
@socket = nil
- on_disconnect
- end
-
- def on_disconnect
end
end
@@ -220,7 +213,7 @@ module Net
end
def error!
- raise @code_type.error_type.new( code + ' ' + Net.quote(msg), self )
+ raise @code_type.error_type.new( code + ' ' + msg.dump, self )
end
end
@@ -305,20 +298,31 @@ module Net
end
def inspect
- "#<#{type}>"
+ "#<#{type} socket=#{@socket.inspect}>"
end
- def write( str )
+ def <<( str )
@socket.__send__ @mid, str
+ self
end
- def <<( str )
+ def write( str )
@socket.__send__ @mid, str
- self
+ end
+
+ alias print write
+
+ def puts( str = '' )
+ @socket.__send__ @mid, str.sub(/\n?/, "\n")
+ end
+
+ def printf( *args )
+ @socket.__send__ @mid, sprintf(*args)
end
end
+
class ReadAdapter
def initialize( block )
@@ -330,25 +334,13 @@ module Net
end
def <<( str )
- callblock( str, &@block ) if @block
+ call_block str, &@block if @block
end
private
- def callblock( str )
- begin
- user_break = true
- yield str
- user_break = false
- rescue Exception
- user_break = false
- raise
- ensure
- if user_break then
- @block = nil
- return # stop breaking
- end
- end
+ def call_block( str )
+ yield str
end
end
@@ -360,7 +352,7 @@ module Net
def initialize( sock )
@socket = sock
@last_reply = nil
- @critical = false
+ @atomic = false
end
attr_accessor :socket
@@ -370,23 +362,20 @@ module Net
"#<#{type}>"
end
- # abstract quit
-
+ # abstract quit()
private
- # abstract get_reply()
-
def check_reply( *oks )
- @last_reply = get_reply
- reply_must( @last_reply, *oks )
+ @last_reply = get_reply()
+ reply_must @last_reply, *oks
end
+ # abstract get_reply()
+
def reply_must( rep, *oks )
oks.each do |i|
- if i === rep then
- return rep
- end
+ return rep if i === rep
end
rep.error!
end
@@ -396,7 +385,6 @@ module Net
check_reply expect
end
-
#
# error handle
#
@@ -404,80 +392,77 @@ module Net
public
def critical?
- @critical
+ @atomic
end
def error_ok
- @critical = false
+ @atomic = false
end
-
private
- def critical
- @critical = true
+ def atomic
+ @atomic = true
ret = yield
- @critical = false
+ @atomic = false
ret
end
- def begin_critical
- ret = @critical
- @critical = true
+ def begin_atomic
+ ret = @atomic
+ @atomic = true
not ret
end
- def end_critical
- @critical = false
+ def end_atomic
+ @atomic = false
end
+ alias critical atomic
+ alias begin_critical begin_atomic
+ alias end_critical end_atomic
+
end
+
class BufferedSocket
- def initialize( addr, port, otime = nil, rtime = nil, dout = nil )
- @addr = addr
- @port = port
+ class << self
+ alias open new
+ end
+ def initialize( addr, port, otime = nil, rtime = nil, dout = nil )
+ @address = addr
+ @port = port
@read_timeout = rtime
-
@debugout = dout
- @socket = nil
- @sending = ''
- @rbuf = ''
+ @socket = nil
+ @rbuf = nil
connect otime
D 'opened'
end
- def connect( otime )
- D "opening connection to #{@addr}..."
- timeout( otime ) {
- @socket = TCPsocket.new( @addr, @port )
- }
- end
- private :connect
-
- attr :pipe, true
+ attr_reader :address
+ attr_reader :port
- class << self
- alias open new
+ def ip_address
+ @socket or return ''
+ @socket.addr[3]
end
- def inspect
- "#<#{type} #{closed? ? 'closed' : 'opened'}>"
- end
+ attr_reader :socket
- def reopen( otime = nil )
- D 'reopening...'
- close
- connect otime
- D 'reopened'
+ def connect( otime )
+ D "opening connection to #{@address}..."
+ timeout( otime ) {
+ @socket = TCPsocket.new( @address, @port )
+ }
+ @rbuf = ''
end
-
- attr :socket, true
+ private :connect
def close
if @socket then
@@ -490,48 +475,43 @@ module Net
@rbuf = ''
end
- def closed?
- not @socket
+ def reopen( otime = nil )
+ D 'reopening...'
+ close
+ connect otime
+ D 'reopened'
end
- def address
- @addr.dup
+ def closed?
+ not @socket
end
- alias addr address
-
- attr_reader :port
-
- def ip_address
- @socket or return ''
- @socket.addr[3]
+ def inspect
+ "#<#{type} #{closed? ? 'closed' : 'opened'}>"
end
- alias ipaddr ip_address
-
- attr_reader :sending
-
+ ###
+ ### READ
+ ###
#
- # input
+ # basic reader
#
public
- CRLF = "\r\n"
-
- def read( len, dest = '', igneof = false )
+ def read( len, dest = '', ignore = false )
D_off "reading #{len} bytes..."
rsize = 0
begin
while rsize + @rbuf.size < len do
- rsize += rbuf_moveto( dest, @rbuf.size )
+ rsize += rbuf_moveto(dest, @rbuf.size)
rbuf_fill
end
rbuf_moveto dest, len - rsize
rescue EOFError
- raise unless igneof
+ raise unless ignore
end
D_on "read #{len} bytes"
@@ -544,7 +524,7 @@ module Net
rsize = 0
begin
while true do
- rsize += rbuf_moveto( dest, @rbuf.size )
+ rsize += rbuf_moveto(dest, @rbuf.size)
rbuf_fill
end
rescue EOFError
@@ -555,28 +535,34 @@ module Net
dest
end
- def readuntil( target, igneof = false )
+ def readuntil( target, ignore = false )
dest = ''
begin
while true do
- idx = @rbuf.index( target )
+ idx = @rbuf.index(target)
break if idx
rbuf_fill
end
rbuf_moveto dest, idx + target.size
rescue EOFError
- raise unless igneof
+ raise unless ignore
rbuf_moveto dest, @rbuf.size
end
dest
end
def readline
- ret = readuntil( "\n" )
+ ret = readuntil("\n")
ret.chop!
ret
end
+ #
+ # line oriented reader
+ #
+
+ public
+
def read_pendstr( dest )
D_off 'reading text...'
@@ -590,7 +576,7 @@ module Net
D_on "read #{rsize} bytes"
dest
end
-
+
# private use only (can not handle 'break')
def read_pendlist
# D_off 'reading list...'
@@ -606,6 +592,10 @@ module Net
# D_on "read #{i} items"
end
+ #
+ # lib (reader)
+ #
+
private
BLOCK_SIZE = 1024 * 2
@@ -623,50 +613,60 @@ module Net
def rbuf_moveto( dest, len )
dest << (s = @rbuf.slice!(0, len))
- @debugout << %Q<read "#{Net.quote s}"\n> if @debugout
+ @debugout << %Q[-> #{s.dump}\n] if @debugout
len
end
+ ###
+ ### WRITE
+ ###
+
#
- # output
+ # basic writer
#
public
def write( str )
writing {
- do_write str
+ do_write str
}
end
def writeline( str )
writing {
- do_write str + "\r\n"
+ do_write str + "\r\n"
}
end
def write_bin( src, block )
writing {
- if block then
- block.call WriteAdapter.new(self, :do_write)
- else
- src.each do |bin|
- do_write bin
+ if block then
+ block.call WriteAdapter.new(self, :do_write)
+ else
+ src.each do |bin|
+ do_write bin
+ end
end
- end
}
end
- def write_pendstr( src, block )
+ #
+ # line oriented writer
+ #
+
+ public
+
+ def write_pendstr( src, &block )
D_off "writing text from #{src.type}"
wsize = using_each_crlf_line {
- if block then
- block.call WriteAdapter.new(self, :wpend_in)
- else
- wpend_in src
- end
+ if block_given? then
+ yield WriteAdapter.new(self, :wpend_in)
+ else
+ wpend_in src
+ end
}
D_on "wrote #{wsize} bytes text"
@@ -688,22 +688,22 @@ module Net
def using_each_crlf_line
writing {
- @wbuf = ''
+ @wbuf = ''
- yield
+ yield
- if not @wbuf.empty? then # unterminated last line
- if @wbuf[-1] == ?\r then
- @wbuf.chop!
+ if not @wbuf.empty? then # unterminated last line
+ if @wbuf[-1] == ?\r then
+ @wbuf.chop!
+ end
+ @wbuf.concat "\r\n"
+ do_write @wbuf
+ elsif @writtensize == 0 then # empty src
+ do_write "\r\n"
end
- @wbuf.concat "\r\n"
- do_write @wbuf
- elsif @writtensize == 0 then # empty src
- do_write "\r\n"
- end
- do_write ".\r\n"
+ do_write ".\r\n"
- @wbuf = nil
+ @wbuf = nil
}
end
@@ -758,34 +758,32 @@ module Net
end
end
+ #
+ # lib (writer)
+ #
+
+ private
def writing
@writtensize = 0
- @sending = ''
-
+ @debugout << '<- ' if @debugout
yield
-
- if @debugout then
- @debugout << 'write "'
- @debugout << @sending
- @debugout << "\"\n"
- end
@socket.flush
+ @debugout << "\n" if @debugout
@writtensize
end
- def do_write( arg )
- if @debugout or @sending.size < 128 then
- @sending << Net.quote( arg )
- else
- @sending << '...' unless @sending[-1] == ?.
- end
-
- s = @socket.write( arg )
- @writtensize += s
- s
+ def do_write( str )
+ @debugout << str.dump if @debugout
+ @writtensize += (n = @socket.write(str))
+ n
end
+ ###
+ ### DEBUG
+ ###
+
+ private
def D_off( msg )
D msg
@@ -806,14 +804,6 @@ module Net
end
- def Net.quote( str )
- str = str.gsub( "\n", '\\n' )
- str.gsub!( "\r", '\\r' )
- str.gsub!( "\t", '\\t' )
- str
- end
-
-
# for backward compatibility
module NetPrivate
Response = ::Net::Response
diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb
index b7b0f6bccc..363fc39088 100644
--- a/lib/net/smtp.rb
+++ b/lib/net/smtp.rb
@@ -92,17 +92,17 @@ like File and Array.
}
}
-=== Giving "Hello" Domain
+=== HELO domain
-If your machine does not have canonical host name, maybe you
-must designate the third argument of SMTP.start.
+In almost all situation, you must designate the third argument
+of SMTP.start/SMTP#start. It is the domain name which you are on
+(the host to send mail from). It is called "HELO domain".
+SMTP server will judge if he/she should send or reject
+the SMTP session by inspecting HELO domain.
Net::SMTP.start( 'your.smtp.server', 25,
'mail.from.domain' ) {|smtp|
-This argument gives MAILFROM domain, the domain name that
-you send mail from. SMTP server might judge if he (or she?)
-send or reject SMTP session by this data.
== class Net::SMTP
@@ -111,8 +111,8 @@ send or reject SMTP session by this data.
: new( address, port = 25 )
creates a new Net::SMTP object.
-: start( address, port = 25, helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil )
-: start( address, port = 25, helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) {|smtp| .... }
+: start( address, port = 25, helo_domain = 'localhost.localdomain', account = nil, password = nil, authtype = nil )
+: start( address, port = 25, helo_domain = 'localhost.localdomain', account = nil, password = nil, authtype = nil ) {|smtp| .... }
is equal to
Net::SMTP.new(address,port).start(helo_domain,account,password,authtype)
@@ -179,8 +179,9 @@ send or reject SMTP session by this data.
: ready( from_addr, *to_addrs ) {|adapter| .... }
This method stands by the SMTP object for sending mail and
- give adapter object to the block. ADAPTER accepts only "write"
- method.
+ gives adapter object to the block. ADAPTER has these 5 methods:
+
+ puts print printf write <<
FROM_ADDR must be a String, representing source mail address.
TO_ADDRS must be Strings or an Array of Strings, representing
@@ -188,11 +189,13 @@ send or reject SMTP session by this data.
# example
Net::SMTP.start( 'your.smtp.server', 25 ) {|smtp|
- smtp.ready( 'from@mail.addr', 'dest@mail.addr' ) do |adapter|
- adapter.write str1
- adapter.write str2
- adapter.write str3
- end
+ smtp.ready( 'from@mail.addr', 'dest@mail.addr' ) {|f|
+ f.puts 'From: aamine@loveruby.net'
+ f.puts 'To: someone@somedomain.org'
+ f.puts 'Subject: test mail'
+ f.puts
+ f.puts 'This is test mail.'
+ }
}
== Exceptions
@@ -215,13 +218,11 @@ require 'digest/md5'
module Net
-
class SMTP < Protocol
protocol_param :port, '25'
protocol_param :command_type, '::Net::SMTPCommand'
-
def initialize( addr, port = nil )
super
@esmtp = true
@@ -229,49 +230,23 @@ module Net
attr :esmtp
- def send_mail( mailsrc, from_addr, *to_addrs )
- do_ready from_addr, to_addrs.flatten
- @command.write_mail mailsrc, nil
- end
-
- alias sendmail send_mail
-
- def ready( from_addr, *to_addrs, &block )
- do_ready from_addr, to_addrs.flatten
- @command.write_mail nil, block
- end
-
-
private
-
- def do_ready( from_addr, to_addrs )
- if to_addrs.empty? then
- raise ArgumentError, 'mail destination does not given'
- end
- @command.mailfrom from_addr
- @command.rcpt to_addrs
- @command.data
- end
-
- def do_start( helodom = nil,
+ def do_start( helo = 'localhost.localdomain',
user = nil, secret = nil, authtype = nil )
- helodom ||= ::Socket.gethostname
- unless helodom then
- raise ArgumentError,
- "cannot get localhost name; try 'smtp.start(local_host_name)'"
- end
+ conn_socket
+ conn_command
begin
if @esmtp then
- @command.ehlo helodom
+ command().ehlo helo
else
- @command.helo helodom
+ command().helo helo
end
rescue ProtocolError
if @esmtp then
@esmtp = false
- @command.error_ok
+ command().error_ok
retry
else
raise
@@ -283,110 +258,133 @@ module Net
raise ArgumentError, 'both of account and password are required'
mid = 'auth_' + (authtype || 'cram_md5').to_s
- @command.respond_to? mid or
+ command().respond_to? mid or
raise ArgumentError, "wrong auth type #{authtype.to_s}"
- @command.__send__ mid, user, secret
+ command().__send__ mid, user, secret
end
end
- end
+ def do_finish
+ disconn_command
+ disconn_socket
+ end
- SMTPSession = SMTP
+ #
+ # SMTP operations
+ #
+
+ public
+
+ def send_mail( mailsrc, from_addr, *to_addrs )
+ do_ready from_addr, to_addrs.flatten
+ command().write_mail mailsrc, nil
+ end
+
+ alias sendmail send_mail
+
+ def ready( from_addr, *to_addrs, &block )
+ do_ready from_addr, to_addrs.flatten
+ command().write_mail nil, block
+ end
+
+ private
+
+ def do_ready( from_addr, to_addrs )
+ if to_addrs.empty? then
+ raise ArgumentError, 'mail destination does not given'
+ end
+ command().mailfrom from_addr
+ command().rcpt to_addrs
+ command().data
+ end
+
+ end
class SMTPCommand < Command
def initialize( sock )
super
- critical {
- check_reply SuccessCode
+ atomic {
+ check_reply SuccessCode
}
end
-
- def helo( fromdom )
- critical {
- getok sprintf( 'HELO %s', fromdom )
+ def helo( domain )
+ atomic {
+ getok sprintf('HELO %s', domain)
}
end
-
- def ehlo( fromdom )
- critical {
- getok sprintf( 'EHLO %s', fromdom )
+ def ehlo( domain )
+ atomic {
+ getok sprintf('EHLO %s', domain)
}
end
-
# "PLAIN" authentication [RFC2554]
def auth_plain( user, secret )
- critical {
- getok sprintf( 'AUTH PLAIN %s',
- ["\0#{user}\0#{secret}"].pack('m').chomp )
+ atomic {
+ getok sprintf('AUTH PLAIN %s',
+ ["\0#{user}\0#{secret}"].pack('m').chomp)
}
end
# "CRAM-MD5" authentication [RFC2195]
def auth_cram_md5( user, secret )
- critical {
- rep = getok( 'AUTH CRAM-MD5', ContinueCode )
- challenge = rep.msg.split(' ')[1].unpack('m')[0]
- secret = Digest::MD5.digest( secret ) if secret.size > 64
-
- isecret = secret + "\0" * (64 - secret.size)
- osecret = isecret.dup
- 0.upto( 63 ) do |i|
- isecret[i] ^= 0x36
- osecret[i] ^= 0x5c
- end
- tmp = Digest::MD5.digest( isecret + challenge )
- tmp = Digest::MD5.hexdigest( osecret + tmp )
-
- getok [user + ' ' + tmp].pack('m').chomp
+ atomic {
+ rep = getok( 'AUTH CRAM-MD5', ContinueCode )
+ challenge = rep.msg.split(' ')[1].unpack('m')[0]
+ secret = Digest::MD5.digest(secret) if secret.size > 64
+
+ isecret = secret + "\0" * (64 - secret.size)
+ osecret = isecret.dup
+ 0.upto( 63 ) do |i|
+ isecret[i] ^= 0x36
+ osecret[i] ^= 0x5c
+ end
+ tmp = Digest::MD5.digest( isecret + challenge )
+ tmp = Digest::MD5.hexdigest( osecret + tmp )
+
+ getok [user + ' ' + tmp].pack('m').chomp
}
end
-
def mailfrom( fromaddr )
- critical {
- getok sprintf( 'MAIL FROM:<%s>', fromaddr )
+ atomic {
+ getok sprintf('MAIL FROM:<%s>', fromaddr)
}
end
-
def rcpt( toaddrs )
toaddrs.each do |i|
- critical {
- getok sprintf( 'RCPT TO:<%s>', i )
+ atomic {
+ getok sprintf('RCPT TO:<%s>', i)
}
end
end
-
def data
- return unless begin_critical
+ return unless begin_atomic
getok 'DATA', ContinueCode
end
def write_mail( mailsrc, block )
- @socket.write_pendstr mailsrc, block
+ @socket.write_pendstr mailsrc, &block
check_reply SuccessCode
- end_critical
+ end_atomic
end
-
def quit
- critical {
- getok 'QUIT'
+ atomic {
+ getok 'QUIT'
}
end
-
private
-
def get_reply
arr = read_reply
stat = arr[0][0,3]
@@ -407,7 +405,6 @@ module Net
Response.new( klass, stat, arr.join('') )
end
-
def read_reply
arr = []
while true do
@@ -424,6 +421,9 @@ module Net
# for backward compatibility
+
+ SMTPSession = SMTP
+
module NetPrivate
SMTPCommand = ::Net::SMTPCommand
end