mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
aamine
* lib/net/protocol.rb: Protocol#start returns the return value of block. * lib/net/protocol.rb: set timeout limit by default. * lib/net/protocol.rb: new methods WriteAdapter#write, puts, print, printf. * lib/net/http.rb: rename HTTP#get2 to request_get, post2 to request_post ... * lib/net/smtp.rb: should not resolve HELO domain automatically. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@1951 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
653f326bb1
commit
f3d9a0cc21
7 changed files with 643 additions and 593 deletions
15
ChangeLog
15
ChangeLog
|
@ -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.'
|
||||
}
|
||||
}
|
||||
|
||||
== 発生する例外
|
||||
|
|
325
lib/net/http.rb
325
lib/net/http.rb
|
@ -215,21 +215,22 @@ Yes, this is not thread-safe.
|
|||
: proxy_port
|
||||
port number of proxy host. If self does not use a proxy, nil.
|
||||
|
||||
: get( path, header = nil, dest = '' )
|
||||
: get( path, header = nil )
|
||||
: get( path, header = nil ) {|str| .... }
|
||||
gets data from PATH on the connecting host.
|
||||
HEADER must be a Hash like { 'Accept' => '*/*', ... }.
|
||||
Response body is written into DEST by using "<<" method.
|
||||
|
||||
This method returns Net::HTTPResponse object.
|
||||
In version 1.1, this method returns a pair of objects,
|
||||
a Net::HTTPResponse object and entity body string.
|
||||
In version 1.2, this method returns a Net::HTTPResponse
|
||||
object.
|
||||
|
||||
If called with block, gives entity body little by little
|
||||
to the block (as String).
|
||||
If called with block, gives entity body string to the block
|
||||
little by little.
|
||||
|
||||
In version 1.1, this method might raises exception for also
|
||||
3xx (redirect). On the case you can get a HTTPResponse object
|
||||
by "anException.response".
|
||||
|
||||
In version 1.2, this method never raises exception.
|
||||
|
||||
# version 1.1 (bundled with Ruby 1.6)
|
||||
|
@ -248,10 +249,6 @@ Yes, this is not thread-safe.
|
|||
f.write str
|
||||
end
|
||||
}
|
||||
# same effect
|
||||
File.open( 'save.txt', 'w' ) {|f|
|
||||
http.get '/~foo/', nil, f
|
||||
}
|
||||
|
||||
: head( path, header = nil )
|
||||
gets only header from PATH on the connecting host.
|
||||
|
@ -262,6 +259,7 @@ Yes, this is not thread-safe.
|
|||
In version 1.1, this method might raises exception for also
|
||||
3xx (redirect). On the case you can get a HTTPResponse object
|
||||
by "anException.response".
|
||||
In version 1.2, this method never raises exception.
|
||||
|
||||
response = nil
|
||||
Net::HTTP.start( 'some.www.server', 80 ) {|http|
|
||||
|
@ -269,67 +267,70 @@ Yes, this is not thread-safe.
|
|||
}
|
||||
p response['content-type']
|
||||
|
||||
: post( path, data, header = nil, dest = '' )
|
||||
: post( path, data, header = nil )
|
||||
: post( path, data, header = nil ) {|str| .... }
|
||||
posts "data" (must be String) to "path".
|
||||
If the body exists, also gets entity body.
|
||||
Response body is written into "dest" by using "<<" method.
|
||||
"header" must be a Hash like { 'Accept' => '*/*', ... }.
|
||||
This method returns Net::HTTPResponse object.
|
||||
posts DATA (must be String) to PATH. HEADER must be a Hash
|
||||
like { 'Accept' => '*/*', ... }.
|
||||
|
||||
In version 1.1, this method returns a pair of objects, a
|
||||
Net::HTTPResponse object and an entity body string.
|
||||
In version 1.2, this method returns a Net::HTTPReponse object.
|
||||
|
||||
If called with block, gives a part of entity body string.
|
||||
|
||||
In version 1.1, this method might raises exception for also
|
||||
3xx (redirect). On the case you can get a HTTPResponse object
|
||||
by "anException.response".
|
||||
In version 1.2, this method never raises exception.
|
||||
|
||||
# version 1.1
|
||||
response, body = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' )
|
||||
response, body = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' )
|
||||
|
||||
# version 1.2
|
||||
response = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' )
|
||||
# compatible for both version
|
||||
response , = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' )
|
||||
response = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' )
|
||||
|
||||
# compatible in both version
|
||||
response , = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' )
|
||||
|
||||
# using block
|
||||
File.open( 'save.html', 'w' ) {|f|
|
||||
http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' ) do |str|
|
||||
http.post( '/cgi-bin/search.rb',
|
||||
'query=subject&target=ruby' ) do |str|
|
||||
f.write str
|
||||
end
|
||||
}
|
||||
# same effect
|
||||
File.open( 'save.html', 'w' ) {|f|
|
||||
http.post '/cgi-bin/search.rb', 'querytype=subject&target=ruby', nil, f
|
||||
}
|
||||
|
||||
: get2( path, header = nil )
|
||||
: get2( path, header = nil ) {|response| .... }
|
||||
: request_get( path, header = nil )
|
||||
: request_get( path, header = nil ) {|response| .... }
|
||||
gets entity from PATH. This method returns a HTTPResponse object.
|
||||
|
||||
When called with block, keep connection while block is executed
|
||||
and gives a HTTPResponse object to the block.
|
||||
|
||||
This method never raise any ProtocolErrors.
|
||||
This method never raises Net::* exceptions.
|
||||
|
||||
# example
|
||||
response = http.get2( '/index.html' )
|
||||
response = http.request_get( '/index.html' )
|
||||
p response['content-type']
|
||||
puts response.body # body is already read
|
||||
|
||||
# using block
|
||||
http.get2( '/index.html' ) {|response|
|
||||
http.request_get( '/index.html' ) {|response|
|
||||
p response['content-type']
|
||||
response.read_body do |str| # read body now
|
||||
print str
|
||||
end
|
||||
}
|
||||
|
||||
: post2( path, header = nil )
|
||||
: post2( path, header = nil ) {|response| .... }
|
||||
: request_post( path, data, header = nil )
|
||||
: request_post( path, data, header = nil ) {|response| .... }
|
||||
posts data to PATH. This method returns a HTTPResponse object.
|
||||
|
||||
When called with block, gives a HTTPResponse object to the block
|
||||
before reading entity body, with keeping connection.
|
||||
|
||||
This method never raises Net::* exceptions.
|
||||
|
||||
# example
|
||||
response = http.post2( '/cgi-bin/nice.rb', 'datadatadata...' )
|
||||
p response.status
|
||||
|
@ -346,12 +347,14 @@ Yes, this is not thread-safe.
|
|||
|
||||
: request( request [, data] )
|
||||
: request( request [, data] ) {|response| .... }
|
||||
sends a HTTPRequest object REQUEST to the (remote) http server.
|
||||
sends a HTTPRequest object REQUEST to the HTTP server.
|
||||
This method also writes DATA string if REQUEST is a post/put request.
|
||||
Giving DATA for get/head request causes ArgumentError.
|
||||
|
||||
If called with block, passes a HTTPResponse object to the block
|
||||
before reading entity body.
|
||||
If called with block, this method passes a HTTPResponse object to
|
||||
the block, without reading entity body.
|
||||
|
||||
This method never raises Net::* exceptions.
|
||||
|
||||
== class Net::HTTP::Get, Head, Post
|
||||
|
||||
|
@ -460,17 +463,39 @@ module Net
|
|||
|
||||
def initialize( addr, port = nil )
|
||||
super
|
||||
|
||||
@curr_http_version = HTTPVersion
|
||||
@seems_1_0_server = false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def conn_command( sock )
|
||||
def do_start
|
||||
conn_socket
|
||||
end
|
||||
|
||||
def do_finish
|
||||
disconn_socket
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# short cut methods
|
||||
#
|
||||
|
||||
def HTTP.get( addr, path, port = nil )
|
||||
req = Get.new( path )
|
||||
resp = nil
|
||||
new( addr, port || HTTP.port ).start {|http|
|
||||
resp = http.request( req )
|
||||
}
|
||||
resp.body
|
||||
end
|
||||
|
||||
def HTTP.get_print( addr, path, port = nil )
|
||||
new( addr, port || HTTP.port ).start {|http|
|
||||
http.get path, nil, $stdout
|
||||
}
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
|
@ -480,7 +505,6 @@ module Net
|
|||
|
||||
public
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
def Proxy( p_addr, p_port = nil )
|
||||
|
@ -496,7 +520,7 @@ module Net
|
|||
def new( address, port = nil, p_addr = nil, p_port = nil )
|
||||
c = p_addr ? self::Proxy(p_addr, p_port) : self
|
||||
i = c.orig_new( address, port )
|
||||
setvar i
|
||||
setimplversion i
|
||||
i
|
||||
end
|
||||
|
||||
|
@ -543,22 +567,26 @@ module Net
|
|||
mod = self
|
||||
klass = Class.new( HTTP )
|
||||
klass.module_eval {
|
||||
include mod
|
||||
@is_proxy_class = true
|
||||
@proxy_address = p_addr
|
||||
@proxy_port = p_port
|
||||
include mod
|
||||
@is_proxy_class = true
|
||||
@proxy_address = p_addr
|
||||
@proxy_port = p_port
|
||||
}
|
||||
klass
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def conn_socket( addr, port )
|
||||
super proxy_address, proxy_port
|
||||
def conn_address
|
||||
proxy_address()
|
||||
end
|
||||
|
||||
def conn_port
|
||||
proxy_port()
|
||||
end
|
||||
|
||||
def edit_path( path )
|
||||
'http://' + addr_port + path
|
||||
'http://' + addr_port() + path
|
||||
end
|
||||
|
||||
end # module ProxyMod
|
||||
|
@ -590,7 +618,7 @@ module Net
|
|||
|
||||
private
|
||||
|
||||
def setvar( obj )
|
||||
def setimplversion( obj )
|
||||
f = @@newimpl
|
||||
obj.instance_eval { @newimpl = f }
|
||||
end
|
||||
|
@ -604,71 +632,92 @@ module Net
|
|||
|
||||
public
|
||||
|
||||
def self.define_http_method_interface( nm, hasdest, hasdata )
|
||||
name = nm.id2name.downcase
|
||||
cname = nm.id2name
|
||||
lineno = __LINE__ + 2
|
||||
src = <<" ----"
|
||||
|
||||
def #{name}( path, #{hasdata ? 'data,' : ''}
|
||||
u_header = nil #{hasdest ? ',dest = nil, &block' : ''} )
|
||||
resp = nil
|
||||
request(
|
||||
#{cname}.new( path, u_header ) #{hasdata ? ',data' : ''}
|
||||
) do |resp|
|
||||
resp.read_body( #{hasdest ? 'dest, &block' : ''} )
|
||||
end
|
||||
if @newimpl then
|
||||
resp
|
||||
else
|
||||
resp.value
|
||||
#{hasdest ? 'return resp, resp.body' : 'resp'}
|
||||
end
|
||||
end
|
||||
|
||||
def #{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
|
||||
|
|
178
lib/net/pop.rb
178
lib/net/pop.rb
|
@ -367,86 +367,96 @@ module Net
|
|||
end
|
||||
|
||||
|
||||
def auth_only( account, password )
|
||||
active? and raise IOError, 'opening already opened POP session'
|
||||
start( account, password ) {
|
||||
;
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# connection
|
||||
#
|
||||
|
||||
def initialize( addr, port = nil, apop = false )
|
||||
super addr, port
|
||||
@mails = nil
|
||||
@apop = false
|
||||
end
|
||||
|
||||
def auth_only( account, password )
|
||||
begin
|
||||
connect
|
||||
@active = true
|
||||
@command.auth address(), port()
|
||||
@command.quit
|
||||
ensure
|
||||
@active = false
|
||||
disconnect
|
||||
end
|
||||
private
|
||||
|
||||
def do_start( account, password )
|
||||
conn_socket
|
||||
@command = (@apop ? type.apop_command_type : type.command_type).new(socket())
|
||||
@command.auth account, password
|
||||
end
|
||||
|
||||
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
|
||||
|
|
194
lib/net/smtp.rb
194
lib/net/smtp.rb
|
@ -92,17 +92,17 @@ like File and Array.
|
|||
}
|
||||
}
|
||||
|
||||
=== Giving "Hello" Domain
|
||||
=== HELO domain
|
||||
|
||||
If your machine does not have canonical host name, maybe you
|
||||
must designate the third argument of SMTP.start.
|
||||
In almost all situation, you must designate the third argument
|
||||
of SMTP.start/SMTP#start. It is the domain name which you are on
|
||||
(the host to send mail from). It is called "HELO domain".
|
||||
SMTP server will judge if he/she should send or reject
|
||||
the SMTP session by inspecting HELO domain.
|
||||
|
||||
Net::SMTP.start( 'your.smtp.server', 25,
|
||||
'mail.from.domain' ) {|smtp|
|
||||
|
||||
This argument gives MAILFROM domain, the domain name that
|
||||
you send mail from. SMTP server might judge if he (or she?)
|
||||
send or reject SMTP session by this data.
|
||||
|
||||
== class Net::SMTP
|
||||
|
||||
|
@ -111,8 +111,8 @@ send or reject SMTP session by this data.
|
|||
: new( address, port = 25 )
|
||||
creates a new Net::SMTP object.
|
||||
|
||||
: start( address, port = 25, helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil )
|
||||
: start( address, port = 25, helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) {|smtp| .... }
|
||||
: start( address, port = 25, helo_domain = 'localhost.localdomain', account = nil, password = nil, authtype = nil )
|
||||
: start( address, port = 25, helo_domain = 'localhost.localdomain', account = nil, password = nil, authtype = nil ) {|smtp| .... }
|
||||
is equal to
|
||||
Net::SMTP.new(address,port).start(helo_domain,account,password,authtype)
|
||||
|
||||
|
@ -179,8 +179,9 @@ send or reject SMTP session by this data.
|
|||
|
||||
: ready( from_addr, *to_addrs ) {|adapter| .... }
|
||||
This method stands by the SMTP object for sending mail and
|
||||
give adapter object to the block. ADAPTER accepts only "write"
|
||||
method.
|
||||
gives adapter object to the block. ADAPTER has these 5 methods:
|
||||
|
||||
puts print printf write <<
|
||||
|
||||
FROM_ADDR must be a String, representing source mail address.
|
||||
TO_ADDRS must be Strings or an Array of Strings, representing
|
||||
|
@ -188,11 +189,13 @@ send or reject SMTP session by this data.
|
|||
|
||||
# example
|
||||
Net::SMTP.start( 'your.smtp.server', 25 ) {|smtp|
|
||||
smtp.ready( 'from@mail.addr', 'dest@mail.addr' ) do |adapter|
|
||||
adapter.write str1
|
||||
adapter.write str2
|
||||
adapter.write str3
|
||||
end
|
||||
smtp.ready( 'from@mail.addr', 'dest@mail.addr' ) {|f|
|
||||
f.puts 'From: aamine@loveruby.net'
|
||||
f.puts 'To: someone@somedomain.org'
|
||||
f.puts 'Subject: test mail'
|
||||
f.puts
|
||||
f.puts 'This is test mail.'
|
||||
}
|
||||
}
|
||||
|
||||
== Exceptions
|
||||
|
@ -215,13 +218,11 @@ require 'digest/md5'
|
|||
|
||||
module Net
|
||||
|
||||
|
||||
class SMTP < Protocol
|
||||
|
||||
protocol_param :port, '25'
|
||||
protocol_param :command_type, '::Net::SMTPCommand'
|
||||
|
||||
|
||||
def initialize( addr, port = nil )
|
||||
super
|
||||
@esmtp = true
|
||||
|
@ -229,49 +230,23 @@ module Net
|
|||
|
||||
attr :esmtp
|
||||
|
||||
def send_mail( mailsrc, from_addr, *to_addrs )
|
||||
do_ready from_addr, to_addrs.flatten
|
||||
@command.write_mail mailsrc, nil
|
||||
end
|
||||
|
||||
alias sendmail send_mail
|
||||
|
||||
def ready( from_addr, *to_addrs, &block )
|
||||
do_ready from_addr, to_addrs.flatten
|
||||
@command.write_mail nil, block
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def do_ready( from_addr, to_addrs )
|
||||
if to_addrs.empty? then
|
||||
raise ArgumentError, 'mail destination does not given'
|
||||
end
|
||||
@command.mailfrom from_addr
|
||||
@command.rcpt to_addrs
|
||||
@command.data
|
||||
end
|
||||
|
||||
def do_start( helodom = nil,
|
||||
def do_start( helo = 'localhost.localdomain',
|
||||
user = nil, secret = nil, authtype = nil )
|
||||
helodom ||= ::Socket.gethostname
|
||||
unless helodom then
|
||||
raise ArgumentError,
|
||||
"cannot get localhost name; try 'smtp.start(local_host_name)'"
|
||||
end
|
||||
conn_socket
|
||||
conn_command
|
||||
|
||||
begin
|
||||
if @esmtp then
|
||||
@command.ehlo helodom
|
||||
command().ehlo helo
|
||||
else
|
||||
@command.helo helodom
|
||||
command().helo helo
|
||||
end
|
||||
rescue ProtocolError
|
||||
if @esmtp then
|
||||
@esmtp = false
|
||||
@command.error_ok
|
||||
command().error_ok
|
||||
retry
|
||||
else
|
||||
raise
|
||||
|
@ -283,110 +258,133 @@ module Net
|
|||
raise ArgumentError, 'both of account and password are required'
|
||||
|
||||
mid = 'auth_' + (authtype || 'cram_md5').to_s
|
||||
@command.respond_to? mid or
|
||||
command().respond_to? mid or
|
||||
raise ArgumentError, "wrong auth type #{authtype.to_s}"
|
||||
|
||||
@command.__send__ mid, user, secret
|
||||
command().__send__ mid, user, secret
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue