* 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
Родитель 653f326bb1
Коммит f3d9a0cc21
7 изменённых файлов: 643 добавлений и 593 удалений

Просмотреть файл

@ -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>
* ext/extmk.rb.in, lib/mkmf.rb (have_library): accept -lm

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -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 #{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?
def get( path, initheader = nil, dest = nil, &block )
res = nil
request( Get.new(path,initheader) ) {|res|
res.read_body dest, &block
}
req.response
unless @newimpl then
res.value
return res, res.body
end
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
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)
}
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
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

Просмотреть файл

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

Просмотреть файл

@ -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
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
# abstract do_finish()
def disconn_command
@command.quit if @command and not @command.critical?
@command = nil
end
def disconnect
@command = nil
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}>"
end
def write( str )
@socket.__send__ @mid, str
"#<#{type} socket=#{@socket.inspect}>"
end
def <<( str )
@socket.__send__ @mid, str
self
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
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
@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
alias open new
end
def inspect
"#<#{type} #{closed? ? 'closed' : 'opened'}>"
end
def initialize( addr, port, otime = nil, rtime = nil, dout = nil )
@address = addr
@port = port
@read_timeout = rtime
@debugout = dout
@socket = nil
@rbuf = nil
def reopen( otime = nil )
D 'reopening...'
close
connect otime
D 'reopened'
D 'opened'
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
if @socket then
@ -490,48 +475,43 @@ module Net
@rbuf = ''
end
def reopen( otime = nil )
D 'reopening...'
close
connect otime
D 'reopened'
end
def closed?
not @socket
end
def address
@addr.dup
def inspect
"#<#{type} #{closed? ? 'closed' : 'opened'}>"
end
alias addr address
attr_reader :port
def ip_address
@socket or return ''
@socket.addr[3]
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

Просмотреть файл

@ -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
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
SMTPSession = SMTP
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
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 )
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
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