diff --git a/ChangeLog b/ChangeLog index 1125ca4e3f..28a34d57a0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +Mon Dec 31 04:27:28 2001 Minero Aoki + + * 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 * ext/extmk.rb.in, lib/mkmf.rb (have_library): accept -lm diff --git a/doc/net/http.rd.ja b/doc/net/http.rd.ja index 3de2c3611a..7569f98f72 100644 --- a/doc/net/http.rd.ja +++ b/doc/net/http.rd.ja @@ -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 はすべて大文字小文字を diff --git a/doc/net/smtp.rd.ja b/doc/net/smtp.rd.ja index fee0acbd36..cc87865dc7 100644 --- a/doc/net/smtp.rd.ja +++ b/doc/net/smtp.rd.ja @@ -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.' + } } == 発生する例外 diff --git a/lib/net/http.rb b/lib/net/http.rb index 3925799005..f2a259a18a 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -215,21 +215,22 @@ Yes, this is not thread-safe. : proxy_port port number of proxy host. If self does not use a proxy, nil. -: get( path, header = nil, dest = '' ) +: get( path, header = nil ) : get( path, header = nil ) {|str| .... } gets data from PATH on the connecting host. HEADER must be a Hash like { 'Accept' => '*/*', ... }. - Response body is written into DEST by using "<<" method. - This method returns Net::HTTPResponse object. + In version 1.1, this method returns a pair of objects, + a Net::HTTPResponse object and entity body string. + In version 1.2, this method returns a Net::HTTPResponse + object. - If called with block, gives entity body little by little - to the block (as String). + If called with block, gives entity body string to the block + little by little. In version 1.1, this method might raises exception for also 3xx (redirect). On the case you can get a HTTPResponse object by "anException.response". - In version 1.2, this method never raises exception. # version 1.1 (bundled with Ruby 1.6) @@ -248,10 +249,6 @@ Yes, this is not thread-safe. f.write str end } - # same effect - File.open( 'save.txt', 'w' ) {|f| - http.get '/~foo/', nil, f - } : head( path, header = nil ) gets only header from PATH on the connecting host. @@ -262,6 +259,7 @@ Yes, this is not thread-safe. In version 1.1, this method might raises exception for also 3xx (redirect). On the case you can get a HTTPResponse object by "anException.response". + In version 1.2, this method never raises exception. response = nil Net::HTTP.start( 'some.www.server', 80 ) {|http| @@ -269,67 +267,70 @@ Yes, this is not thread-safe. } p response['content-type'] -: post( path, data, header = nil, dest = '' ) +: post( path, data, header = nil ) : post( path, data, header = nil ) {|str| .... } - posts "data" (must be String) to "path". - If the body exists, also gets entity body. - Response body is written into "dest" by using "<<" method. - "header" must be a Hash like { 'Accept' => '*/*', ... }. - This method returns Net::HTTPResponse object. + posts DATA (must be String) to PATH. HEADER must be a Hash + like { 'Accept' => '*/*', ... }. + + In version 1.1, this method returns a pair of objects, a + Net::HTTPResponse object and an entity body string. + In version 1.2, this method returns a Net::HTTPReponse object. If called with block, gives a part of entity body string. In version 1.1, this method might raises exception for also 3xx (redirect). On the case you can get a HTTPResponse object by "anException.response". + In version 1.2, this method never raises exception. # version 1.1 - response, body = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' ) + response, body = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' ) + # version 1.2 - response = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' ) - # compatible for both version - response , = http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' ) + response = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' ) + + # compatible in both version + response , = http.post( '/cgi-bin/search.rb', 'query=subject&target=ruby' ) # using block File.open( 'save.html', 'w' ) {|f| - http.post( '/cgi-bin/search.rb', 'querytype=subject&target=ruby' ) do |str| + http.post( '/cgi-bin/search.rb', + 'query=subject&target=ruby' ) do |str| f.write str end } - # same effect - File.open( 'save.html', 'w' ) {|f| - http.post '/cgi-bin/search.rb', 'querytype=subject&target=ruby', nil, f - } -: get2( path, header = nil ) -: get2( path, header = nil ) {|response| .... } +: request_get( path, header = nil ) +: request_get( path, header = nil ) {|response| .... } gets entity from PATH. This method returns a HTTPResponse object. When called with block, keep connection while block is executed and gives a HTTPResponse object to the block. - This method never raise any ProtocolErrors. + This method never raises Net::* exceptions. # example - response = http.get2( '/index.html' ) + response = http.request_get( '/index.html' ) p response['content-type'] puts response.body # body is already read # using block - http.get2( '/index.html' ) {|response| + http.request_get( '/index.html' ) {|response| p response['content-type'] response.read_body do |str| # read body now print str end } -: post2( path, header = nil ) -: post2( path, header = nil ) {|response| .... } +: request_post( path, data, header = nil ) +: request_post( path, data, header = nil ) {|response| .... } posts data to PATH. This method returns a HTTPResponse object. When called with block, gives a HTTPResponse object to the block before reading entity body, with keeping connection. + This method never raises Net::* exceptions. + # example response = http.post2( '/cgi-bin/nice.rb', 'datadatadata...' ) p response.status @@ -346,12 +347,14 @@ Yes, this is not thread-safe. : request( request [, data] ) : request( request [, data] ) {|response| .... } - sends a HTTPRequest object REQUEST to the (remote) http server. + sends a HTTPRequest object REQUEST to the HTTP server. This method also writes DATA string if REQUEST is a post/put request. Giving DATA for get/head request causes ArgumentError. - If called with block, passes a HTTPResponse object to the block - before reading entity body. + If called with block, this method passes a HTTPResponse object to + the block, without reading entity body. + + This method never raises Net::* exceptions. == class Net::HTTP::Get, Head, Post @@ -460,17 +463,39 @@ module Net def initialize( addr, port = nil ) super - @curr_http_version = HTTPVersion @seems_1_0_server = false end private - def conn_command( sock ) + def do_start + conn_socket end def do_finish + disconn_socket + end + + + # + # short cut methods + # + + def HTTP.get( addr, path, port = nil ) + req = Get.new( path ) + resp = nil + new( addr, port || HTTP.port ).start {|http| + resp = http.request( req ) + } + resp.body + end + + def HTTP.get_print( addr, path, port = nil ) + new( addr, port || HTTP.port ).start {|http| + http.get path, nil, $stdout + } + nil end @@ -480,7 +505,6 @@ module Net public - class << self def Proxy( p_addr, p_port = nil ) @@ -496,7 +520,7 @@ module Net def new( address, port = nil, p_addr = nil, p_port = nil ) c = p_addr ? self::Proxy(p_addr, p_port) : self i = c.orig_new( address, port ) - setvar i + setimplversion i i end @@ -543,22 +567,26 @@ module Net mod = self klass = Class.new( HTTP ) klass.module_eval { - include mod - @is_proxy_class = true - @proxy_address = p_addr - @proxy_port = p_port + include mod + @is_proxy_class = true + @proxy_address = p_addr + @proxy_port = p_port } klass end private - def conn_socket( addr, port ) - super proxy_address, proxy_port + def conn_address + proxy_address() + end + + def conn_port + proxy_port() end def edit_path( path ) - 'http://' + addr_port + path + 'http://' + addr_port() + path end end # module ProxyMod @@ -590,7 +618,7 @@ module Net private - def setvar( obj ) + def setimplversion( obj ) f = @@newimpl obj.instance_eval { @newimpl = f } end @@ -604,71 +632,92 @@ module Net public - def self.define_http_method_interface( nm, hasdest, hasdata ) - name = nm.id2name.downcase - cname = nm.id2name - lineno = __LINE__ + 2 - src = <<" ----" - - def #{name}( path, #{hasdata ? 'data,' : ''} - u_header = nil #{hasdest ? ',dest = nil, &block' : ''} ) - resp = nil - request( - #{cname}.new( path, u_header ) #{hasdata ? ',data' : ''} - ) do |resp| - resp.read_body( #{hasdest ? 'dest, &block' : ''} ) - end - if @newimpl then - resp - else - resp.value - #{hasdest ? 'return resp, resp.body' : 'resp'} - end - end - - def #{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 diff --git a/lib/net/pop.rb b/lib/net/pop.rb index 02729ca259..1dbbc9aeb0 100644 --- a/lib/net/pop.rb +++ b/lib/net/pop.rb @@ -367,86 +367,96 @@ module Net end + def auth_only( account, password ) + active? and raise IOError, 'opening already opened POP session' + start( account, password ) { + ; + } + end + + + # + # connection + # + def initialize( addr, port = nil, apop = false ) super addr, port @mails = nil @apop = false end - def auth_only( account, password ) - begin - connect - @active = true - @command.auth address(), port() - @command.quit - ensure - @active = false - disconnect - end + private + + def do_start( account, password ) + conn_socket + @command = (@apop ? type.apop_command_type : type.command_type).new(socket()) + @command.auth account, password end - 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 diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb index 4f95434f99..f21bdf72f1 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -28,27 +28,17 @@ module Net Version = '1.2.3' Revision = %q$Revision$.split(/\s+/)[1] + class << self - def start( address, port = nil, *args ) - instance = new( address, port ) - - if block_given? then - instance.start( *args ) { yield instance } - else - instance.start( *args ) - instance - end - end - 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 if @debugout + @debugout << %Q[-> #{s.dump}\n] if @debugout len end + ### + ### WRITE + ### + # - # output + # basic writer # public def write( str ) writing { - do_write str + do_write str } end def writeline( str ) writing { - do_write str + "\r\n" + do_write str + "\r\n" } end def write_bin( src, block ) writing { - if block then - block.call WriteAdapter.new(self, :do_write) - else - src.each do |bin| - do_write bin + if block then + block.call WriteAdapter.new(self, :do_write) + else + src.each do |bin| + do_write bin + end end - end } end - def write_pendstr( src, block ) + # + # line oriented writer + # + + public + + def write_pendstr( src, &block ) D_off "writing text from #{src.type}" wsize = using_each_crlf_line { - if block then - block.call WriteAdapter.new(self, :wpend_in) - else - wpend_in src - end + if block_given? then + yield WriteAdapter.new(self, :wpend_in) + else + wpend_in src + end } D_on "wrote #{wsize} bytes text" @@ -688,22 +688,22 @@ module Net def using_each_crlf_line writing { - @wbuf = '' + @wbuf = '' - yield + yield - if not @wbuf.empty? then # unterminated last line - if @wbuf[-1] == ?\r then - @wbuf.chop! + if not @wbuf.empty? then # unterminated last line + if @wbuf[-1] == ?\r then + @wbuf.chop! + end + @wbuf.concat "\r\n" + do_write @wbuf + elsif @writtensize == 0 then # empty src + do_write "\r\n" end - @wbuf.concat "\r\n" - do_write @wbuf - elsif @writtensize == 0 then # empty src - do_write "\r\n" - end - do_write ".\r\n" + do_write ".\r\n" - @wbuf = nil + @wbuf = nil } end @@ -758,34 +758,32 @@ module Net end end + # + # lib (writer) + # + + private def writing @writtensize = 0 - @sending = '' - + @debugout << '<- ' if @debugout yield - - if @debugout then - @debugout << 'write "' - @debugout << @sending - @debugout << "\"\n" - end @socket.flush + @debugout << "\n" if @debugout @writtensize end - def do_write( arg ) - if @debugout or @sending.size < 128 then - @sending << Net.quote( arg ) - else - @sending << '...' unless @sending[-1] == ?. - end - - s = @socket.write( arg ) - @writtensize += s - s + def do_write( str ) + @debugout << str.dump if @debugout + @writtensize += (n = @socket.write(str)) + n end + ### + ### DEBUG + ### + + private def D_off( msg ) D msg @@ -806,14 +804,6 @@ module Net end - def Net.quote( str ) - str = str.gsub( "\n", '\\n' ) - str.gsub!( "\r", '\\r' ) - str.gsub!( "\t", '\\t' ) - str - end - - # for backward compatibility module NetPrivate Response = ::Net::Response diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb index b7b0f6bccc..363fc39088 100644 --- a/lib/net/smtp.rb +++ b/lib/net/smtp.rb @@ -92,17 +92,17 @@ like File and Array. } } -=== Giving "Hello" Domain +=== HELO domain -If your machine does not have canonical host name, maybe you -must designate the third argument of SMTP.start. +In almost all situation, you must designate the third argument +of SMTP.start/SMTP#start. It is the domain name which you are on +(the host to send mail from). It is called "HELO domain". +SMTP server will judge if he/she should send or reject +the SMTP session by inspecting HELO domain. Net::SMTP.start( 'your.smtp.server', 25, 'mail.from.domain' ) {|smtp| -This argument gives MAILFROM domain, the domain name that -you send mail from. SMTP server might judge if he (or she?) -send or reject SMTP session by this data. == class Net::SMTP @@ -111,8 +111,8 @@ send or reject SMTP session by this data. : new( address, port = 25 ) creates a new Net::SMTP object. -: start( address, port = 25, helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) -: start( address, port = 25, helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) {|smtp| .... } +: start( address, port = 25, helo_domain = 'localhost.localdomain', account = nil, password = nil, authtype = nil ) +: start( address, port = 25, helo_domain = 'localhost.localdomain', account = nil, password = nil, authtype = nil ) {|smtp| .... } is equal to Net::SMTP.new(address,port).start(helo_domain,account,password,authtype) @@ -179,8 +179,9 @@ send or reject SMTP session by this data. : ready( from_addr, *to_addrs ) {|adapter| .... } This method stands by the SMTP object for sending mail and - give adapter object to the block. ADAPTER accepts only "write" - method. + gives adapter object to the block. ADAPTER has these 5 methods: + + puts print printf write << FROM_ADDR must be a String, representing source mail address. TO_ADDRS must be Strings or an Array of Strings, representing @@ -188,11 +189,13 @@ send or reject SMTP session by this data. # example Net::SMTP.start( 'your.smtp.server', 25 ) {|smtp| - smtp.ready( 'from@mail.addr', 'dest@mail.addr' ) do |adapter| - adapter.write str1 - adapter.write str2 - adapter.write str3 - end + smtp.ready( 'from@mail.addr', 'dest@mail.addr' ) {|f| + f.puts 'From: aamine@loveruby.net' + f.puts 'To: someone@somedomain.org' + f.puts 'Subject: test mail' + f.puts + f.puts 'This is test mail.' + } } == Exceptions @@ -215,13 +218,11 @@ require 'digest/md5' module Net - class SMTP < Protocol protocol_param :port, '25' protocol_param :command_type, '::Net::SMTPCommand' - def initialize( addr, port = nil ) super @esmtp = true @@ -229,49 +230,23 @@ module Net attr :esmtp - def send_mail( mailsrc, from_addr, *to_addrs ) - do_ready from_addr, to_addrs.flatten - @command.write_mail mailsrc, nil - end - - alias sendmail send_mail - - def ready( from_addr, *to_addrs, &block ) - do_ready from_addr, to_addrs.flatten - @command.write_mail nil, block - end - - private - - def do_ready( from_addr, to_addrs ) - if to_addrs.empty? then - raise ArgumentError, 'mail destination does not given' - end - @command.mailfrom from_addr - @command.rcpt to_addrs - @command.data - end - - def do_start( helodom = nil, + def do_start( helo = 'localhost.localdomain', user = nil, secret = nil, authtype = nil ) - helodom ||= ::Socket.gethostname - unless helodom then - raise ArgumentError, - "cannot get localhost name; try 'smtp.start(local_host_name)'" - end + conn_socket + conn_command begin if @esmtp then - @command.ehlo helodom + command().ehlo helo else - @command.helo helodom + command().helo helo end rescue ProtocolError if @esmtp then @esmtp = false - @command.error_ok + command().error_ok retry else raise @@ -283,110 +258,133 @@ module Net raise ArgumentError, 'both of account and password are required' mid = 'auth_' + (authtype || 'cram_md5').to_s - @command.respond_to? mid or + command().respond_to? mid or raise ArgumentError, "wrong auth type #{authtype.to_s}" - @command.__send__ mid, user, secret + command().__send__ mid, user, secret end end + 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