* 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
This commit is contained in:
aamine 2001-12-30 19:18:45 +00:00
parent 653f326bb1
commit f3d9a0cc21
7 changed files with 643 additions and 593 deletions

View File

@ -1,3 +1,18 @@
Mon Dec 31 04:27:28 2001 Minero Aoki <aamine@mx.edit.ne.jp>
* 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.
Sun Dec 30 00:59:16 2001 WATANABE Hirofumi <eban@ruby-lang.org> Sun Dec 30 00:59:16 2001 WATANABE Hirofumi <eban@ruby-lang.org>
* ext/extmk.rb.in, lib/mkmf.rb (have_library): accept -lm * ext/extmk.rb.in, lib/mkmf.rb (have_library): accept -lm

View File

@ -204,15 +204,16 @@ Ruby 1.6
プロクシ経由で接続する HTTP オブジェクトならプロクシのポート。 プロクシ経由で接続する HTTP オブジェクトならプロクシのポート。
そうでないなら nil。 そうでないなら nil。
: get( path, header = nil, dest = '' ) : get( path, header = nil )
: get( path, header = nil ) {|str| .... } : get( path, header = nil ) {|str| .... }
サーバ上の path にあるエンティティを取得し、dest に << メソッドを サーバ上の path にあるエンティティを取得します。また header が nil
使って書きこみます。また header が nil でなければリクエストを送る でなければリクエストを送るときにその内容を HTTP ヘッダとして書き
ときにその内容を HTTP ヘッダとして書きこみます。header はハッシュで、 こみます。header はハッシュで、「ヘッダ名 => 内容」のような形式で
「ヘッダ名 => 内容」のような形式でなければいけません。 なければいけません。
返り値は、バージョン 1.1 では HTTPResponse と dest 二要素の配列です。 返り値は、バージョン 1.1 では HTTPResponse とエンティティボディ文字列の
1.2 では HTTPResponse ただひとつのみです。 二要素の配列です。1.2 では HTTPResponse ただひとつのみです。この場合、
エンティティボディは response.body で得られます。
ブロックとともに呼ばれた時はエンティティボディを少しづつブロックに ブロックとともに呼ばれた時はエンティティボディを少しづつブロックに
与えます。 与えます。
@ -237,10 +238,6 @@ Ruby 1.6
f.write str f.write str
end end
} }
# same effect
File.open( 'save.txt', 'w' ) {|f|
http.get '/~foo/', nil, f
}
: head( path, header = nil ) : head( path, header = nil )
サーバ上の path にあるエンティティのヘッダのみを取得します。 サーバ上の path にあるエンティティのヘッダのみを取得します。
@ -260,7 +257,7 @@ Ruby 1.6
} }
p response['content-type'] p response['content-type']
: post( path, data, header = nil, dest = '' ) : post( path, data, header = nil )
: post( path, data, header = nil ) {|str| .... } : post( path, data, header = nil ) {|str| .... }
サーバ上の path にあるエンティティに対し文字列 data を サーバ上の path にあるエンティティに対し文字列 data を
送ります。レスポンスは << メソッドを使って dest に書き 送ります。レスポンスは << メソッドを使って dest に書き
@ -275,55 +272,54 @@ Ruby 1.6
一方 1.2 では全く例外を発生しません。 一方 1.2 では全く例外を発生しません。
# version 1.1 # 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 # version 1.2
response = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' ) response = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' )
# compatible for both version
response , = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' ) # compatible in both version
response , = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' )
# using block # using block
File.open( 'save.html', 'w' ) {|f| 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 f.write str
end 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 ) : request_get( path, header = nil )
: get2( path, header = nil ) {|response| .... } : request_get( path, header = nil ) {|response| .... }
path にあるエンティティを取得します。HTTPResponse path にあるエンティティを取得します。HTTPResponse
オブジェクトを返します。 オブジェクトを返します。
ブロックとともに呼び出されたときは、ブロック実行中は接続を ブロックとともに呼び出されたときは、ブロック実行中は接続を
維持したまま HTTPResponse オブジェクトをブロックに渡します。 維持したまま HTTPResponse オブジェクトをブロックに渡します。
このメソッドはステータスに関らず例外を発生させません。 このメソッドは HTTP プロトコルに関連した例外は発生させません。
# example # example
response = http.get2( '/index.html' ) response = http.request_get( '/index.html' )
p response['content-type'] p response['content-type']
puts response.body # body is already read puts response.body # body is already read
# using block # using block
http.get2( '/index.html' ) {|response| http.request_get( '/index.html' ) {|response|
p response['content-type'] p response['content-type']
response.read_body do |str| # read body now response.read_body do |str| # read body now
print str print str
end end
} }
: post2( path, header = nil ) : request_post( path, data, header = nil )
: post2( path, header = nil ) {|response| .... } : request_post( path, data, header = nil ) {|response| .... }
path にあるエンティティを取得します。HTTPResponse path にあるエンティティを取得します。HTTPResponse
オブジェクトを返します。 オブジェクトを返します。
ブロックとともに呼び出されたときは、ボディを読みこむ前に ブロックとともに呼び出されたときは、ボディを読みこむ前に
HTTPResponse オブジェクトをブロックに渡します。 HTTPResponse オブジェクトをブロックに渡します。
このメソッドはステータスに関らず例外を発生させません。 このメソッドは HTTP プロトコルに関連した例外は発生させません。
# example # example
response = http.post2( '/cgi-bin/nice.rb', 'datadatadata...' ) response = http.post2( '/cgi-bin/nice.rb', 'datadatadata...' )
@ -341,12 +337,14 @@ Ruby 1.6
: request( request [, data] ) : request( request [, data] )
: request( request [, data] ) {|response| .... } : request( request [, data] ) {|response| .... }
リクエストオブジェクト request を送信します。POST の時は data も HTTPResquest オブジェクト request を送信します。POST/PUT の時は data も
与えられます。(POST 以外で data を与えると ArgumentError を発生します) 与えられます (POST/PUT 以外で data を与えると ArgumentError を発生します)
ブロックとともに呼びだされたときはボディを読みこまずに HTTPResponse ブロックとともに呼びだされたときはボディを読みこまずに HTTPResponse
オブジェクトをブロックに与えます。 オブジェクトをブロックに与えます。
このメソッドは HTTP プロトコルに関連した例外は発生させません。
== class Net::HTTP::Get, Head, Post == class Net::HTTP::Get, Head, Post
HTTP リクエストを抽象化するクラス。key はすべて大文字小文字を HTTP リクエストを抽象化するクラス。key はすべて大文字小文字を

View File

@ -76,17 +76,25 @@ each
} }
} }
=== Hello ドメイン === HELO ドメイン
SMTP ではメールを送る側のホストの名前を要求されるのですが、 SMTP ではメールを送る側のホストの名前 (HELO ドメインと呼ぶ) を要求
ダイヤルアップなどの場合には自分のマシンに正式な名前がない場合が されるのですが、Net::SMTP ではとりあえず localhost.localdomain と
あります。そのような場合は適宜 SMTP サーバの名前などを与えてやら いう名前を送信しています。たいていの SMTP サーバはこの HELO ドメイン
ないと配送を拒否されることがあります。SMTP.start あるいは SMTP#start による認証はあまり真面目に行わないので (簡単に偽造できるからです)
の引数 helo_domain がそれです。 問題にならないことが多いのですが、まれにメールセッションを切られる
こともあります。そういうときはとりあえず HELO ドメインを与えてみて
ください。もちろんそれ以外の時も HELO ドメインはちゃんと渡すのが
ベストです。
HELO ドメインは SMTP.start/SMTP#start の第三引数 helo_domain に指定
します。
Net::SMTP.start( 'your.smtp.server', 25, Net::SMTP.start( 'your.smtp.server', 25,
'mail.from.domain' ) {|smtp| 'mail.from.domain' ) {|smtp|
よくあるダイヤルアップホストの場合、HELO ドメインには ISP のメール
サーバのドメインを使っておけばたいてい通ります。
== class Net::SMTP == class Net::SMTP
@ -96,8 +104,8 @@ SMTP
新しい SMTP オブジェクトを生成します。address はSMTPサーバーのFQDNで、 新しい SMTP オブジェクトを生成します。address はSMTPサーバーのFQDNで、
port は接続するポート番号です。ただし、このメソッドではまだ接続はしません。 port は接続するポート番号です。ただし、このメソッドではまだ接続はしません。
: start( address, port = 25, helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) : start( address, port = 25, helo_domain = 'localhost.localdomain', 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 ) {|smtp| .... }
以下と同じです。 以下と同じです。
Net::SMTP.new(address,port).start(helo_domain,account,password,authtype) Net::SMTP.new(address,port).start(helo_domain,account,password,authtype)
@ -161,11 +169,13 @@ SMTP
# example # example
Net::SMTP.start( 'your.smtp.server', 25 ) {|smtp| Net::SMTP.start( 'your.smtp.server', 25 ) {|smtp|
smtp.ready( 'from@mail.addr', 'dest@mail.addr' ) do |adapter| smtp.ready( 'from@mail.addr', 'dest@mail.addr' ) {|f|
adapter.write str1 f.puts 'From: aamine@loveruby.net'
adapter.write str2 f.puts 'To: someone@somedomain.org'
adapter.write str3 f.puts 'Subject: test mail'
end f.puts
f.puts 'This is test mail.'
}
} }
== 発生する例外 == 発生する例外

View File

@ -215,21 +215,22 @@ Yes, this is not thread-safe.
: proxy_port : proxy_port
port number of proxy host. If self does not use a proxy, nil. 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| .... } : get( path, header = nil ) {|str| .... }
gets data from PATH on the connecting host. gets data from PATH on the connecting host.
HEADER must be a Hash like { 'Accept' => '*/*', ... }. 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 If called with block, gives entity body string to the block
to the block (as String). little by little.
In version 1.1, this method might raises exception for also In version 1.1, this method might raises exception for also
3xx (redirect). On the case you can get a HTTPResponse object 3xx (redirect). On the case you can get a HTTPResponse object
by "anException.response". by "anException.response".
In version 1.2, this method never raises exception. In version 1.2, this method never raises exception.
# version 1.1 (bundled with Ruby 1.6) # version 1.1 (bundled with Ruby 1.6)
@ -248,10 +249,6 @@ Yes, this is not thread-safe.
f.write str f.write str
end end
} }
# same effect
File.open( 'save.txt', 'w' ) {|f|
http.get '/~foo/', nil, f
}
: head( path, header = nil ) : head( path, header = nil )
gets only header from PATH on the connecting host. 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 In version 1.1, this method might raises exception for also
3xx (redirect). On the case you can get a HTTPResponse object 3xx (redirect). On the case you can get a HTTPResponse object
by "anException.response". by "anException.response".
In version 1.2, this method never raises exception.
response = nil response = nil
Net::HTTP.start( 'some.www.server', 80 ) {|http| Net::HTTP.start( 'some.www.server', 80 ) {|http|
@ -269,67 +267,70 @@ Yes, this is not thread-safe.
} }
p response['content-type'] p response['content-type']
: post( path, data, header = nil, dest = '' ) : post( path, data, header = nil )
: post( path, data, header = nil ) {|str| .... } : post( path, data, header = nil ) {|str| .... }
posts "data" (must be String) to "path". posts DATA (must be String) to PATH. HEADER must be a Hash
If the body exists, also gets entity body. like { 'Accept' => '*/*', ... }.
Response body is written into "dest" by using "<<" method.
"header" must be a Hash like { 'Accept' => '*/*', ... }. In version 1.1, this method returns a pair of objects, a
This method returns Net::HTTPResponse object. 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. If called with block, gives a part of entity body string.
In version 1.1, this method might raises exception for also In version 1.1, this method might raises exception for also
3xx (redirect). On the case you can get a HTTPResponse object 3xx (redirect). On the case you can get a HTTPResponse object
by "anException.response". by "anException.response".
In version 1.2, this method never raises exception.
# version 1.1 # 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 # version 1.2
response = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' ) response = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' )
# compatible for both version
response , = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' ) # compatible in both version
response , = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' )
# using block # using block
File.open( 'save.html', 'w' ) {|f| 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 f.write str
end 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 ) : request_get( path, header = nil )
: get2( path, header = nil ) {|response| .... } : request_get( path, header = nil ) {|response| .... }
gets entity from PATH. This method returns a HTTPResponse object. gets entity from PATH. This method returns a HTTPResponse object.
When called with block, keep connection while block is executed When called with block, keep connection while block is executed
and gives a HTTPResponse object to the block. and gives a HTTPResponse object to the block.
This method never raise any ProtocolErrors. This method never raises Net::* exceptions.
# example # example
response = http.get2( '/index.html' ) response = http.request_get( '/index.html' )
p response['content-type'] p response['content-type']
puts response.body # body is already read puts response.body # body is already read
# using block # using block
http.get2( '/index.html' ) {|response| http.request_get( '/index.html' ) {|response|
p response['content-type'] p response['content-type']
response.read_body do |str| # read body now response.read_body do |str| # read body now
print str print str
end end
} }
: post2( path, header = nil ) : request_post( path, data, header = nil )
: post2( path, header = nil ) {|response| .... } : request_post( path, data, header = nil ) {|response| .... }
posts data to PATH. This method returns a HTTPResponse object. posts data to PATH. This method returns a HTTPResponse object.
When called with block, gives a HTTPResponse object to the block When called with block, gives a HTTPResponse object to the block
before reading entity body, with keeping connection. before reading entity body, with keeping connection.
This method never raises Net::* exceptions.
# example # example
response = http.post2( '/cgi-bin/nice.rb', 'datadatadata...' ) response = http.post2( '/cgi-bin/nice.rb', 'datadatadata...' )
p response.status p response.status
@ -346,12 +347,14 @@ Yes, this is not thread-safe.
: request( request [, data] ) : request( request [, data] )
: request( request [, data] ) {|response| .... } : 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. This method also writes DATA string if REQUEST is a post/put request.
Giving DATA for get/head request causes ArgumentError. Giving DATA for get/head request causes ArgumentError.
If called with block, passes a HTTPResponse object to the block If called with block, this method passes a HTTPResponse object to
before reading entity body. the block, without reading entity body.
This method never raises Net::* exceptions.
== class Net::HTTP::Get, Head, Post == class Net::HTTP::Get, Head, Post
@ -460,17 +463,39 @@ module Net
def initialize( addr, port = nil ) def initialize( addr, port = nil )
super super
@curr_http_version = HTTPVersion @curr_http_version = HTTPVersion
@seems_1_0_server = false @seems_1_0_server = false
end end
private private
def conn_command( sock ) def do_start
conn_socket
end end
def do_finish 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 end
@ -480,7 +505,6 @@ module Net
public public
class << self class << self
def Proxy( p_addr, p_port = nil ) def Proxy( p_addr, p_port = nil )
@ -496,7 +520,7 @@ module Net
def new( address, port = nil, p_addr = nil, p_port = nil ) def new( address, port = nil, p_addr = nil, p_port = nil )
c = p_addr ? self::Proxy(p_addr, p_port) : self c = p_addr ? self::Proxy(p_addr, p_port) : self
i = c.orig_new( address, port ) i = c.orig_new( address, port )
setvar i setimplversion i
i i
end end
@ -543,22 +567,26 @@ module Net
mod = self mod = self
klass = Class.new( HTTP ) klass = Class.new( HTTP )
klass.module_eval { klass.module_eval {
include mod include mod
@is_proxy_class = true @is_proxy_class = true
@proxy_address = p_addr @proxy_address = p_addr
@proxy_port = p_port @proxy_port = p_port
} }
klass klass
end end
private private
def conn_socket( addr, port ) def conn_address
super proxy_address, proxy_port proxy_address()
end
def conn_port
proxy_port()
end end
def edit_path( path ) def edit_path( path )
'http://' + addr_port + path 'http://' + addr_port() + path
end end
end # module ProxyMod end # module ProxyMod
@ -590,7 +618,7 @@ module Net
private private
def setvar( obj ) def setimplversion( obj )
f = @@newimpl f = @@newimpl
obj.instance_eval { @newimpl = f } obj.instance_eval { @newimpl = f }
end end
@ -604,71 +632,92 @@ module Net
public public
def self.define_http_method_interface( nm, hasdest, hasdata ) def get( path, initheader = nil, dest = nil, &block )
name = nm.id2name.downcase res = nil
cname = nm.id2name request( Get.new(path,initheader) ) {|res|
lineno = __LINE__ + 2 res.read_body dest, &block
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 #{name}2( path, #{hasdata ? 'data,' : ''}
u_header = nil, &block )
request( #{cname}.new(path, u_header),
#{hasdata ? 'data,' : ''} &block )
end
----
module_eval src, __FILE__, lineno
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 request( req, body = nil, &block )
unless active? then
start {
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.response unless @newimpl then
res.value
return res, res.body
end
res
end 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
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 ) def send_request( name, path, body = nil, header = nil )
r = HTTPGenericRequest.new( name, (body ? true : false), true, r = HTTPGenericRequest.new( name, (body ? true : false), true,
path, header ) path, header )
request r, body request r, body
end end
def request( req, body = nil, &block )
unless active? then
start {
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.response
end
private private
def connecting( req ) def connecting( req )
if @socket.closed? then if @socket.closed? then
re_connect reconn_socket
end end
if not req.body_exist? or @seems_1_0_server then if not req.body_exist? or @seems_1_0_server then
req['connection'] = 'close' req['connection'] = 'close'
@ -712,25 +761,6 @@ module Net
# utils # 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 private
def addr_port def addr_port
@ -746,8 +776,6 @@ module Net
end end
HTTPSession = HTTP
class Code class Code
@ -894,9 +922,7 @@ module Net
end end
def range=( r, fin = nil ) def range=( r, fin = nil )
if fin then r = (r ... r + fin) if fin
r = r ... r+fin
end
case r case r
when Numeric when Numeric
@ -970,7 +996,7 @@ module Net
include HTTPHeader include HTTPHeader
def initialize( m, reqbody, resbody, path, uhead = nil ) def initialize( m, reqbody, resbody, path, initheader = nil )
@method = m @method = m
@request_has_body = reqbody @request_has_body = reqbody
@response_has_body = resbody @response_has_body = resbody
@ -978,8 +1004,8 @@ module Net
@response = nil @response = nil
@header = tmp = {} @header = tmp = {}
return unless uhead return unless initheader
uhead.each do |k,v| initheader.each do |k,v|
key = k.downcase key = k.downcase
if tmp.key? key then if tmp.key? key then
$stderr.puts "WARNING: duplicated HTTP header: #{k}" if $VERBOSE $stderr.puts "WARNING: duplicated HTTP header: #{k}" if $VERBOSE
@ -1023,7 +1049,7 @@ module Net
check_arg_n body check_arg_n body
sendreq_no_body sock, ver, path sendreq_no_body sock, ver, path
end end
@response = r = get_response( sock ) @response = r = get_response(sock)
r r
end end
@ -1033,12 +1059,8 @@ module Net
end end
def check_arg_b( data, block ) def check_arg_b( data, block )
if data and block then (data and block) and raise ArgumentError, 'both of data and block given'
raise ArgumentError, 'both of data and block given' (data or block) or raise ArgumentError, 'str or block required'
end
unless data or block then
raise ArgumentError, 'str or block required'
end
end end
def check_arg_n( data ) def check_arg_n( data )
@ -1094,11 +1116,11 @@ module Net
class HTTPRequest < HTTPGenericRequest class HTTPRequest < HTTPGenericRequest
def initialize( path, uhead = nil ) def initialize( path, initheader = nil )
super type::METHOD, super type::METHOD,
type::REQUEST_HAS_BODY, type::REQUEST_HAS_BODY,
type::RESPONSE_HAS_BODY, type::RESPONSE_HAS_BODY,
path, uhead path, initheader
end end
end end
@ -1226,7 +1248,7 @@ module Net
while true do while true do
line = sock.readuntil( "\n", true ) # ignore EOF 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? break if line.empty?
m = /\A([^:]+):\s*/.match( line ) m = /\A([^:]+):\s*/.match( line )
@ -1298,22 +1320,22 @@ module Net
# #
def read_body( dest = nil, &block ) def read_body( dest = nil, &block )
if @read and (dest or block) then if @read then
raise IOError, "#{type}\#read_body called twice with argument" (dest or block) and
raise IOError, "#{type}\#read_body called twice with argument"
return @body
end end
unless @read then to = procdest(dest, block)
to = procdest( dest, block ) stream_check
stream_check
if @body_exist and code_type.body_exist? then if @body_exist and code_type.body_exist? then
read_body_0 to read_body_0 to
@body = to @body = to
else else
@body = nil @body = nil
end
@read = true
end end
@read = true
@body @body
end end
@ -1321,10 +1343,8 @@ module Net
alias body read_body alias body read_body
alias entity read_body alias entity read_body
private private
def terminate def terminate
read_body read_body
end end
@ -1353,11 +1373,11 @@ module Net
while true do while true do
line = @socket.readline 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}" m or raise HTTPBadResponse, "wrong chunk size line: #{line}"
len = m[0].hex len = m[0].hex
break if len == 0 break if len == 0
@socket.read( len, dest ); total += len @socket.read len, dest; total += len
@socket.read 2 # \r\n @socket.read 2 # \r\n
end end
until @socket.readline.empty? do until @socket.readline.empty? do
@ -1384,6 +1404,9 @@ module Net
# for backward compatibility # for backward compatibility
HTTPSession = HTTP
module NetPrivate module NetPrivate
HTTPResponse = ::Net::HTTPResponse HTTPResponse = ::Net::HTTPResponse
HTTPGenericRequest = ::Net::HTTPGenericRequest HTTPGenericRequest = ::Net::HTTPGenericRequest

View File

@ -367,86 +367,96 @@ module Net
end 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 ) def initialize( addr, port = nil, apop = false )
super addr, port super addr, port
@mails = nil @mails = nil
@apop = false @apop = false
end end
def auth_only( account, password ) private
begin
connect def do_start( account, password )
@active = true conn_socket
@command.auth address(), port() @command = (@apop ? type.apop_command_type : type.command_type).new(socket())
@command.quit @command.auth account, password
ensure
@active = false
disconnect
end
end end
attr :mails def do_finish
@mails = nil
disconn_command
disconn_socket
end
#
# 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 ) def each_mail( &block )
io_check mails().each( &block )
@mails.each( &block )
end end
alias each each_mail alias each each_mail
def delete_all def delete_all
io_check mails().each do |m|
@mails.each do |m|
yield m if block_given? yield m if block_given?
m.delete unless m.deleted? m.delete unless m.deleted?
end end
end end
def reset def reset
io_check command().rset
@command.rset mails().each do |m|
@mails.each do |m|
m.instance_eval { @deleted = false } m.instance_eval { @deleted = false }
end end
end end
private def command
io_check
def conn_command( sock ) super
@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
end end
def io_check def io_check
(not @socket or @socket.closed?) and (not socket() or socket().closed?) and
raise IOError, 'pop session is not opened yet' raise IOError, 'POP session is not opened yet'
end end
end end
POP = POP3 POP = POP3
POPSession = POP3
POP3Session = POP3
class APOP < POP3 class APOP < POP3
protocol_param :command_type, '::Net::APOPCommand' protocol_param :command_type, '::Net::APOPCommand'
end end
APOPSession = APOP
class POPMail class POPMail
@ -500,86 +510,84 @@ module Net
end end
class POP3Command < Command class POP3Command < Command
def initialize( sock ) def initialize( sock )
super super
critical { atomic {
check_reply SuccessCode check_reply SuccessCode
} }
end end
def auth( account, pass ) def auth( account, pass )
critical { atomic {
@socket.writeline 'USER ' + account @socket.writeline 'USER ' + account
check_reply_auth check_reply_auth
@socket.writeline 'PASS ' + pass @socket.writeline 'PASS ' + pass
check_reply_auth check_reply_auth
} }
end end
def list def list
arr = [] arr = []
critical { atomic {
getok 'LIST' getok 'LIST'
@socket.read_pendlist do |line| @socket.read_pendlist do |line|
m = /\A(\d+)[ \t]+(\d+)/.match(line) or m = /\A(\d+)[ \t]+(\d+)/.match(line) or
raise BadResponse, "illegal response: #{line}" raise BadResponse, "illegal response: #{line}"
arr[ m[1].to_i ] = m[2].to_i arr[ m[1].to_i ] = m[2].to_i
end end
} }
arr arr
end end
def rset def rset
critical { atomic {
getok 'RSET' getok 'RSET'
} }
end end
def top( num, lines = 0, dest = '' ) def top( num, lines = 0, dest = '' )
critical { atomic {
getok sprintf( 'TOP %d %d', num, lines ) getok sprintf( 'TOP %d %d', num, lines )
@socket.read_pendstr dest @socket.read_pendstr dest
} }
end end
def retr( num, dest = '', &block ) def retr( num, dest = '', &block )
critical { atomic {
getok sprintf('RETR %d', num) getok sprintf('RETR %d', num)
@socket.read_pendstr dest, &block @socket.read_pendstr dest, &block
} }
end end
def dele( num ) def dele( num )
critical { atomic {
getok sprintf('DELE %d', num) getok sprintf('DELE %d', num)
} }
end end
def uidl( num ) def uidl( num )
critical { atomic {
getok( sprintf('UIDL %d', num) ).msg.split(' ')[1] getok( sprintf('UIDL %d', num) ).msg.split(' ')[1]
} }
end end
def quit def quit
critical { atomic {
getok 'QUIT' getok 'QUIT'
} }
end end
private private
def check_reply_auth def check_reply_auth
begin begin
return check_reply( SuccessCode ) return check_reply(SuccessCode)
rescue ProtocolError => err rescue ProtocolError => err
raise ProtoAuthError.new( 'Fail to POP authentication', err.response ) raise ProtoAuthError.new('Fail to POP authentication', err.response)
end end
end end
@ -599,22 +607,28 @@ module Net
class APOPCommand < POP3Command class APOPCommand < POP3Command
def initialize( sock ) def initialize( sock )
rep = super( sock ) response = super(sock)
m = /<.+>/.match(response.msg) or
m = /<.+>/.match( rep.msg ) or raise ProtoAuthError.new("not APOP server: cannot login", nil)
raise ProtoAuthError.new( "not APOP server: cannot login", nil )
@stamp = m[0] @stamp = m[0]
end end
def auth( account, pass ) def auth( account, pass )
critical { atomic {
@socket.writeline sprintf( 'APOP %s %s', @socket.writeline sprintf('APOP %s %s',
account, account,
Digest::MD5.hexdigest(@stamp + pass) ) Digest::MD5.hexdigest(@stamp + pass))
check_reply_auth check_reply_auth
} }
end end
end end
# for backward compatibility
POPSession = POP3
POP3Session = POP3
APOPSession = APOP
end # module Net end # module Net

View File

@ -28,27 +28,17 @@ module Net
Version = '1.2.3' Version = '1.2.3'
Revision = %q$Revision$.split(/\s+/)[1] Revision = %q$Revision$.split(/\s+/)[1]
class << self 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
private private
def protocol_param( name, val ) def protocol_param( name, val )
module_eval %- module_eval <<-End, __FILE__, __LINE__ + 1
def self.#{name.id2name} def self.#{name.id2name}
#{val} #{val}
end end
- End
end end
end end
@ -61,11 +51,11 @@ module Net
# protocol_param command_type # protocol_param command_type
# protocol_param socket_type (optional) # protocol_param socket_type (optional)
# #
# private method do_start (optional) # private method do_start
# private method do_finish (optional) # private method do_finish
# #
# private method on_connect (optional) # private method conn_address
# private method on_disconnect (optional) # private method conn_port
# #
protocol_param :port, 'nil' protocol_param :port, 'nil'
@ -73,6 +63,19 @@ module Net
protocol_param :socket_type, '::Net::BufferedSocket' 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 ) def initialize( addr, port = nil )
@address = addr @address = addr
@port = port || type.port @port = port || type.port
@ -82,8 +85,8 @@ module Net
@active = false @active = false
@open_timeout = nil @open_timeout = 30
@read_timeout = nil @read_timeout = 60
@dout = nil @dout = nil
end end
@ -112,90 +115,80 @@ module Net
end end
# #
# open session # open
# #
def start( *args ) 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 if block_given? then
begin begin
_start args do_start( *args )
yield self @active = true
return yield(self)
ensure ensure
finish if active? finish if @active
end end
else
_start args
end end
do_start( *args )
@active = true
nil nil
end end
private private
def _start( args ) # abstract do_start()
connect
do_start( *args )
@active = true
end
def connect def conn_socket
conn_socket @address, @port @socket = type.socket_type.open(
conn_address(), conn_port(),
@open_timeout, @read_timeout, @dout )
on_connect on_connect
conn_command @socket
end end
def re_connect alias conn_address address
alias conn_port port
def reconn_socket
@socket.reopen @open_timeout @socket.reopen @open_timeout
on_connect on_connect
end end
def conn_socket( addr, port ) def conn_command
@socket = type.socket_type.open( @command = type.command_type.new(@socket)
addr, port, @open_timeout, @read_timeout, @dout )
end
def conn_command( sock )
@command = type.command_type.new( sock )
end end
def on_connect def on_connect
end end
def do_start
end
# #
# close session # close
# #
public public
def finish def finish
active? or raise IOError, 'already closed protocol' active? or raise IOError, 'closing already closed protocol'
do_finish
do_finish if @command and not @command.critical?
disconnect
@active = false @active = false
nil nil
end end
private private
def do_finish # abstract do_finish()
@command.quit
def disconn_command
@command.quit if @command and not @command.critical?
@command = nil
end end
def disconnect def disconn_socket
@command = nil
if @socket and not @socket.closed? then if @socket and not @socket.closed? then
@socket.close @socket.close
end end
@socket = nil @socket = nil
on_disconnect
end
def on_disconnect
end end
end end
@ -220,7 +213,7 @@ module Net
end end
def error! 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
end end
@ -305,20 +298,31 @@ module Net
end end
def inspect def inspect
"#<#{type}>" "#<#{type} socket=#{@socket.inspect}>"
end
def write( str )
@socket.__send__ @mid, str
end end
def <<( str ) def <<( str )
@socket.__send__ @mid, str @socket.__send__ @mid, str
self self
end end
def write( str )
@socket.__send__ @mid, str
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 end
class ReadAdapter class ReadAdapter
def initialize( block ) def initialize( block )
@ -330,25 +334,13 @@ module Net
end end
def <<( str ) def <<( str )
callblock( str, &@block ) if @block call_block str, &@block if @block
end end
private private
def callblock( str ) def call_block( str )
begin yield str
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
end end
end end
@ -360,7 +352,7 @@ module Net
def initialize( sock ) def initialize( sock )
@socket = sock @socket = sock
@last_reply = nil @last_reply = nil
@critical = false @atomic = false
end end
attr_accessor :socket attr_accessor :socket
@ -370,23 +362,20 @@ module Net
"#<#{type}>" "#<#{type}>"
end end
# abstract quit # abstract quit()
private private
# abstract get_reply()
def check_reply( *oks ) def check_reply( *oks )
@last_reply = get_reply @last_reply = get_reply()
reply_must( @last_reply, *oks ) reply_must @last_reply, *oks
end end
# abstract get_reply()
def reply_must( rep, *oks ) def reply_must( rep, *oks )
oks.each do |i| oks.each do |i|
if i === rep then return rep if i === rep
return rep
end
end end
rep.error! rep.error!
end end
@ -396,7 +385,6 @@ module Net
check_reply expect check_reply expect
end end
# #
# error handle # error handle
# #
@ -404,80 +392,77 @@ module Net
public public
def critical? def critical?
@critical @atomic
end end
def error_ok def error_ok
@critical = false @atomic = false
end end
private private
def critical def atomic
@critical = true @atomic = true
ret = yield ret = yield
@critical = false @atomic = false
ret ret
end end
def begin_critical def begin_atomic
ret = @critical ret = @atomic
@critical = true @atomic = true
not ret not ret
end end
def end_critical def end_atomic
@critical = false @atomic = false
end end
alias critical atomic
alias begin_critical begin_atomic
alias end_critical end_atomic
end end
class BufferedSocket class BufferedSocket
def initialize( addr, port, otime = nil, rtime = nil, dout = nil )
@addr = addr
@port = port
@read_timeout = rtime
@debugout = dout
@socket = nil
@sending = ''
@rbuf = ''
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
class << self class << self
alias open new alias open new
end end
def inspect def initialize( addr, port, otime = nil, rtime = nil, dout = nil )
"#<#{type} #{closed? ? 'closed' : 'opened'}>" @address = addr
end @port = port
@read_timeout = rtime
@debugout = dout
@socket = nil
@rbuf = nil
def reopen( otime = nil )
D 'reopening...'
close
connect otime connect otime
D 'reopened' D 'opened'
end end
attr :socket, true attr_reader :address
attr_reader :port
def ip_address
@socket or return ''
@socket.addr[3]
end
attr_reader :socket
def connect( otime )
D "opening connection to #{@address}..."
timeout( otime ) {
@socket = TCPsocket.new( @address, @port )
}
@rbuf = ''
end
private :connect
def close def close
if @socket then if @socket then
@ -490,48 +475,43 @@ module Net
@rbuf = '' @rbuf = ''
end end
def reopen( otime = nil )
D 'reopening...'
close
connect otime
D 'reopened'
end
def closed? def closed?
not @socket not @socket
end end
def address def inspect
@addr.dup "#<#{type} #{closed? ? 'closed' : 'opened'}>"
end end
alias addr address ###
### READ
attr_reader :port ###
def ip_address
@socket or return ''
@socket.addr[3]
end
alias ipaddr ip_address
attr_reader :sending
# #
# input # basic reader
# #
public public
CRLF = "\r\n" def read( len, dest = '', ignore = false )
def read( len, dest = '', igneof = false )
D_off "reading #{len} bytes..." D_off "reading #{len} bytes..."
rsize = 0 rsize = 0
begin begin
while rsize + @rbuf.size < len do while rsize + @rbuf.size < len do
rsize += rbuf_moveto( dest, @rbuf.size ) rsize += rbuf_moveto(dest, @rbuf.size)
rbuf_fill rbuf_fill
end end
rbuf_moveto dest, len - rsize rbuf_moveto dest, len - rsize
rescue EOFError rescue EOFError
raise unless igneof raise unless ignore
end end
D_on "read #{len} bytes" D_on "read #{len} bytes"
@ -544,7 +524,7 @@ module Net
rsize = 0 rsize = 0
begin begin
while true do while true do
rsize += rbuf_moveto( dest, @rbuf.size ) rsize += rbuf_moveto(dest, @rbuf.size)
rbuf_fill rbuf_fill
end end
rescue EOFError rescue EOFError
@ -555,28 +535,34 @@ module Net
dest dest
end end
def readuntil( target, igneof = false ) def readuntil( target, ignore = false )
dest = '' dest = ''
begin begin
while true do while true do
idx = @rbuf.index( target ) idx = @rbuf.index(target)
break if idx break if idx
rbuf_fill rbuf_fill
end end
rbuf_moveto dest, idx + target.size rbuf_moveto dest, idx + target.size
rescue EOFError rescue EOFError
raise unless igneof raise unless ignore
rbuf_moveto dest, @rbuf.size rbuf_moveto dest, @rbuf.size
end end
dest dest
end end
def readline def readline
ret = readuntil( "\n" ) ret = readuntil("\n")
ret.chop! ret.chop!
ret ret
end end
#
# line oriented reader
#
public
def read_pendstr( dest ) def read_pendstr( dest )
D_off 'reading text...' D_off 'reading text...'
@ -590,7 +576,7 @@ module Net
D_on "read #{rsize} bytes" D_on "read #{rsize} bytes"
dest dest
end end
# private use only (can not handle 'break') # private use only (can not handle 'break')
def read_pendlist def read_pendlist
# D_off 'reading list...' # D_off 'reading list...'
@ -606,6 +592,10 @@ module Net
# D_on "read #{i} items" # D_on "read #{i} items"
end end
#
# lib (reader)
#
private private
BLOCK_SIZE = 1024 * 2 BLOCK_SIZE = 1024 * 2
@ -623,50 +613,60 @@ module Net
def rbuf_moveto( dest, len ) def rbuf_moveto( dest, len )
dest << (s = @rbuf.slice!(0, len)) dest << (s = @rbuf.slice!(0, len))
@debugout << %Q<read "#{Net.quote s}"\n> if @debugout @debugout << %Q[-> #{s.dump}\n] if @debugout
len len
end end
###
### WRITE
###
# #
# output # basic writer
# #
public public
def write( str ) def write( str )
writing { writing {
do_write str do_write str
} }
end end
def writeline( str ) def writeline( str )
writing { writing {
do_write str + "\r\n" do_write str + "\r\n"
} }
end end
def write_bin( src, block ) def write_bin( src, block )
writing { writing {
if block then if block then
block.call WriteAdapter.new(self, :do_write) block.call WriteAdapter.new(self, :do_write)
else else
src.each do |bin| src.each do |bin|
do_write bin do_write bin
end
end 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}" D_off "writing text from #{src.type}"
wsize = using_each_crlf_line { wsize = using_each_crlf_line {
if block then if block_given? then
block.call WriteAdapter.new(self, :wpend_in) yield WriteAdapter.new(self, :wpend_in)
else else
wpend_in src wpend_in src
end end
} }
D_on "wrote #{wsize} bytes text" D_on "wrote #{wsize} bytes text"
@ -688,22 +688,22 @@ module Net
def using_each_crlf_line def using_each_crlf_line
writing { writing {
@wbuf = '' @wbuf = ''
yield yield
if not @wbuf.empty? then # unterminated last line if not @wbuf.empty? then # unterminated last line
if @wbuf[-1] == ?\r then if @wbuf[-1] == ?\r then
@wbuf.chop! @wbuf.chop!
end
@wbuf.concat "\r\n"
do_write @wbuf
elsif @writtensize == 0 then # empty src
do_write "\r\n"
end end
@wbuf.concat "\r\n" do_write ".\r\n"
do_write @wbuf
elsif @writtensize == 0 then # empty src
do_write "\r\n"
end
do_write ".\r\n"
@wbuf = nil @wbuf = nil
} }
end end
@ -758,34 +758,32 @@ module Net
end end
end end
#
# lib (writer)
#
private
def writing def writing
@writtensize = 0 @writtensize = 0
@sending = '' @debugout << '<- ' if @debugout
yield yield
if @debugout then
@debugout << 'write "'
@debugout << @sending
@debugout << "\"\n"
end
@socket.flush @socket.flush
@debugout << "\n" if @debugout
@writtensize @writtensize
end end
def do_write( arg ) def do_write( str )
if @debugout or @sending.size < 128 then @debugout << str.dump if @debugout
@sending << Net.quote( arg ) @writtensize += (n = @socket.write(str))
else n
@sending << '...' unless @sending[-1] == ?.
end
s = @socket.write( arg )
@writtensize += s
s
end end
###
### DEBUG
###
private
def D_off( msg ) def D_off( msg )
D msg D msg
@ -806,14 +804,6 @@ module Net
end end
def Net.quote( str )
str = str.gsub( "\n", '\\n' )
str.gsub!( "\r", '\\r' )
str.gsub!( "\t", '\\t' )
str
end
# for backward compatibility # for backward compatibility
module NetPrivate module NetPrivate
Response = ::Net::Response Response = ::Net::Response

View File

@ -92,17 +92,17 @@ like File and Array.
} }
} }
=== Giving "Hello" Domain === HELO domain
If your machine does not have canonical host name, maybe you In almost all situation, you must designate the third argument
must designate the third argument of SMTP.start. 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, Net::SMTP.start( 'your.smtp.server', 25,
'mail.from.domain' ) {|smtp| '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 == class Net::SMTP
@ -111,8 +111,8 @@ send or reject SMTP session by this data.
: new( address, port = 25 ) : new( address, port = 25 )
creates a new Net::SMTP object. 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 = 'localhost.localdomain', 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 ) {|smtp| .... }
is equal to is equal to
Net::SMTP.new(address,port).start(helo_domain,account,password,authtype) 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| .... } : ready( from_addr, *to_addrs ) {|adapter| .... }
This method stands by the SMTP object for sending mail and This method stands by the SMTP object for sending mail and
give adapter object to the block. ADAPTER accepts only "write" gives adapter object to the block. ADAPTER has these 5 methods:
method.
puts print printf write <<
FROM_ADDR must be a String, representing source mail address. FROM_ADDR must be a String, representing source mail address.
TO_ADDRS must be Strings or an Array of Strings, representing TO_ADDRS must be Strings or an Array of Strings, representing
@ -188,11 +189,13 @@ send or reject SMTP session by this data.
# example # example
Net::SMTP.start( 'your.smtp.server', 25 ) {|smtp| Net::SMTP.start( 'your.smtp.server', 25 ) {|smtp|
smtp.ready( 'from@mail.addr', 'dest@mail.addr' ) do |adapter| smtp.ready( 'from@mail.addr', 'dest@mail.addr' ) {|f|
adapter.write str1 f.puts 'From: aamine@loveruby.net'
adapter.write str2 f.puts 'To: someone@somedomain.org'
adapter.write str3 f.puts 'Subject: test mail'
end f.puts
f.puts 'This is test mail.'
}
} }
== Exceptions == Exceptions
@ -215,13 +218,11 @@ require 'digest/md5'
module Net module Net
class SMTP < Protocol class SMTP < Protocol
protocol_param :port, '25' protocol_param :port, '25'
protocol_param :command_type, '::Net::SMTPCommand' protocol_param :command_type, '::Net::SMTPCommand'
def initialize( addr, port = nil ) def initialize( addr, port = nil )
super super
@esmtp = true @esmtp = true
@ -229,49 +230,23 @@ module Net
attr :esmtp 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 private
def do_start( helo = 'localhost.localdomain',
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,
user = nil, secret = nil, authtype = nil ) user = nil, secret = nil, authtype = nil )
helodom ||= ::Socket.gethostname conn_socket
unless helodom then conn_command
raise ArgumentError,
"cannot get localhost name; try 'smtp.start(local_host_name)'"
end
begin begin
if @esmtp then if @esmtp then
@command.ehlo helodom command().ehlo helo
else else
@command.helo helodom command().helo helo
end end
rescue ProtocolError rescue ProtocolError
if @esmtp then if @esmtp then
@esmtp = false @esmtp = false
@command.error_ok command().error_ok
retry retry
else else
raise raise
@ -283,110 +258,133 @@ module Net
raise ArgumentError, 'both of account and password are required' raise ArgumentError, 'both of account and password are required'
mid = 'auth_' + (authtype || 'cram_md5').to_s 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}" raise ArgumentError, "wrong auth type #{authtype.to_s}"
@command.__send__ mid, user, secret command().__send__ mid, user, secret
end end
end end
def do_finish
disconn_command
disconn_socket
end
#
# 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 end
SMTPSession = SMTP
class SMTPCommand < Command class SMTPCommand < Command
def initialize( sock ) def initialize( sock )
super super
critical { atomic {
check_reply SuccessCode check_reply SuccessCode
} }
end end
def helo( domain )
def helo( fromdom ) atomic {
critical { getok sprintf('HELO %s', domain)
getok sprintf( 'HELO %s', fromdom )
} }
end end
def ehlo( domain )
def ehlo( fromdom ) atomic {
critical { getok sprintf('EHLO %s', domain)
getok sprintf( 'EHLO %s', fromdom )
} }
end end
# "PLAIN" authentication [RFC2554] # "PLAIN" authentication [RFC2554]
def auth_plain( user, secret ) def auth_plain( user, secret )
critical { atomic {
getok sprintf( 'AUTH PLAIN %s', getok sprintf('AUTH PLAIN %s',
["\0#{user}\0#{secret}"].pack('m').chomp ) ["\0#{user}\0#{secret}"].pack('m').chomp)
} }
end end
# "CRAM-MD5" authentication [RFC2195] # "CRAM-MD5" authentication [RFC2195]
def auth_cram_md5( user, secret ) def auth_cram_md5( user, secret )
critical { atomic {
rep = getok( 'AUTH CRAM-MD5', ContinueCode ) rep = getok( 'AUTH CRAM-MD5', ContinueCode )
challenge = rep.msg.split(' ')[1].unpack('m')[0] challenge = rep.msg.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)
osecret = isecret.dup osecret = isecret.dup
0.upto( 63 ) do |i| 0.upto( 63 ) do |i|
isecret[i] ^= 0x36 isecret[i] ^= 0x36
osecret[i] ^= 0x5c osecret[i] ^= 0x5c
end end
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').chomp getok [user + ' ' + tmp].pack('m').chomp
} }
end end
def mailfrom( fromaddr ) def mailfrom( fromaddr )
critical { atomic {
getok sprintf( '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|
critical { atomic {
getok sprintf( 'RCPT TO:<%s>', i ) getok sprintf('RCPT TO:<%s>', i)
} }
end end
end end
def data def data
return unless begin_critical return unless begin_atomic
getok 'DATA', ContinueCode getok 'DATA', ContinueCode
end end
def write_mail( mailsrc, block ) def write_mail( mailsrc, block )
@socket.write_pendstr mailsrc, block @socket.write_pendstr mailsrc, &block
check_reply SuccessCode check_reply SuccessCode
end_critical end_atomic
end end
def quit def quit
critical { atomic {
getok 'QUIT' getok 'QUIT'
} }
end end
private private
def get_reply def get_reply
arr = read_reply arr = read_reply
stat = arr[0][0,3] stat = arr[0][0,3]
@ -407,7 +405,6 @@ module Net
Response.new( klass, stat, arr.join('') ) Response.new( klass, stat, arr.join('') )
end end
def read_reply def read_reply
arr = [] arr = []
while true do while true do
@ -424,6 +421,9 @@ module Net
# for backward compatibility # for backward compatibility
SMTPSession = SMTP
module NetPrivate module NetPrivate
SMTPCommand = ::Net::SMTPCommand SMTPCommand = ::Net::SMTPCommand
end end