diff options
author | aamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2002-01-15 03:34:59 +0000 |
---|---|---|
committer | aamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2002-01-15 03:34:59 +0000 |
commit | a7d98b7bc323f5915bab6a1984989e60f5686584 (patch) | |
tree | 6d2e04619f8b0a63b0e53cd456415215fc3f170a /lib | |
parent | 0ef2cab2f23d098152b745da3791bf47301b5ed6 (diff) |
aamine
* lib/net/protocol.rb: Protocol#start returns the return value of block.
* lib/net/protocol.rb: set timeout limit by default.
* lib/net/protocol.rb: new methods WriteAdapter#write, puts, print, printf.
* lib/net/protocol.rb: change template method scheme.
* lib/net/http.rb: rename HTTP#get2 to request_get, post2 to request_post ...
* lib/net/pop.rb: POP#auth_only did not work.
* lib/net/smtp.rb: should not resolve HELO domain automatically.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_1_6@1992 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib')
-rw-r--r-- | lib/net/http.rb | 355 | ||||
-rw-r--r-- | lib/net/pop.rb | 235 | ||||
-rw-r--r-- | lib/net/protocol.rb | 487 | ||||
-rw-r--r-- | lib/net/smtp.rb | 206 |
4 files changed, 655 insertions, 628 deletions
diff --git a/lib/net/http.rb b/lib/net/http.rb index 27cb0fbc3c..4bf907cc23 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1,6 +1,6 @@ =begin -= net/http.rb version 1.1.37 += net/http.rb Copyright (c) 1999-2001 Yukihiro Matsumoto @@ -14,6 +14,8 @@ Ruby Distribute License or GNU General Public License. NOTE: You can find Japanese version of this document in the doc/net directory of the standard ruby interpreter package. +$Id$ + == What is this module? This module provide your program the functions to access WWW @@ -213,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) @@ -246,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. @@ -260,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| @@ -267,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 @@ -411,17 +414,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 @@ -431,7 +456,6 @@ module Net public - class << self def Proxy( p_addr, p_port = nil ) @@ -447,7 +471,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 @@ -494,22 +518,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 @@ -541,7 +569,7 @@ module Net private - def setvar( obj ) + def setimplversion( obj ) f = @@newimpl obj.instance_eval { @newimpl = f } end @@ -555,72 +583,92 @@ module Net public - def self.define_http_method_interface( nm, hasdest, hasdata ) - name = nm.id2name.downcase - cname = nm.id2name - lineno = __LINE__ + 2 - src = <<" ----" - - def #{name}( path, #{hasdata ? 'data,' : ''} - u_header = nil #{hasdest ? ',dest = nil, &block' : ''} ) - resp = nil - request( - #{cname}.new( path, u_header ) #{hasdata ? ',data' : ''} - ) do |resp| - resp.read_body( #{hasdest ? 'dest, &block' : ''} ) - end - if @newimpl then - resp - else - resp.value - #{hasdest ? 'return resp, resp.body' : 'resp'} - end - end + def get( path, initheader = nil, dest = nil, &block ) + res = nil + request( Get.new(path,initheader) ) {|res| + res.read_body dest, &block + } + unless @newimpl then + res.value + return res, res.body + end - def #{name}2( path, #{hasdata ? 'data,' : ''} - u_header = nil, &block ) - request( #{cname}.new(path, u_header), - #{hasdata ? 'data,' : ''} &block ) - end - ---- - module_eval src, __FILE__, lineno + res + end + + def head( path, initheader = nil ) + res = request( Head.new(path,initheader) ) + @newimpl or res.value + res + end + + def post( path, data, initheader = nil, dest = nil, &block ) + res = nil + request( Post.new(path,initheader), data ) {|res| + res.read_body dest, &block + } + unless @newimpl then + res.value + return res, res.body + end + + res + end + + def put( path, data, initheader = nil ) + res = request( Put.new(path,initheader), data ) + @newimpl or res.value + res end - define_http_method_interface :Get, true, false - define_http_method_interface :Head, false, false - define_http_method_interface :Post, true, true - define_http_method_interface :Put, false, true + def request_get( path, initheader = nil, &block ) + request Get.new(path,initheader), &block + end + + def request_head( path, initheader = nil, &block ) + request Head.new(path,initheader), &block + end + + def request_post( path, data, initheader = nil, &block ) + request Post.new(path,initheader), data, &block + end + + def request_put( path, data, initheader = nil, &block ) + request Put.new(path,initheader), data, &block + end + + alias get2 request_get + alias head2 request_head + alias post2 request_post + alias put2 request_put + + def send_request( name, path, body = nil, header = nil ) + r = HTTPGenericRequest.new( name, (body ? true : false), true, + path, header ) + request r, body + end def request( req, body = nil, &block ) unless active? then start { - req['connection'] = 'close' - return request(req, body, &block) + req['connection'] = 'close' + return request(req, body, &block) } end connecting( req ) { - req.__send__( :exec, - @socket, @curr_http_version, edit_path(req.path), body ) - yield req.response if block_given? + req.__send__( :exec, + @socket, @curr_http_version, edit_path(req.path), body ) + yield req.response if block_given? } req.response end - def send_request( name, path, body = nil, header = nil ) - r = ::Net::NetPrivate::HTTPGenericRequest.new( - name, (body ? true : false), true, - path, header ) - request r, body - end - - private - def connecting( req ) if @socket.closed? then - re_connect + reconn_socket end if not req.body_exist? or @seems_1_0_server then req['connection'] = 'close' @@ -664,25 +712,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 @@ -698,8 +727,6 @@ module Net end - HTTPSession = HTTP - class Code @@ -771,8 +798,6 @@ module Net ### header ### - net_private { - module HTTPHeader def size @@ -789,10 +814,12 @@ module Net @header[ key.downcase ] = val end - def each( &block ) + def each_header( &block ) @header.each( &block ) end + alias each each_header + def each_key( &block ) @header.each_key( &block ) end @@ -846,9 +873,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 @@ -920,9 +945,9 @@ module Net class HTTPGenericRequest - include ::Net::NetPrivate::HTTPHeader + 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 @@ -930,8 +955,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 @@ -975,7 +1000,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 @@ -985,12 +1010,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 ) @@ -1013,6 +1034,11 @@ module Net @header['content-length'] = data.size.to_s @header.delete 'transfer-encoding' + unless @header['content-type'] then + $stderr.puts 'Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE + @header['content-type'] = 'application/x-www-form-urlencoded' + end + request sock, ver, path sock.write data end @@ -1031,8 +1057,7 @@ module Net def get_response( sock ) begin - resp = ::Net::NetPrivate::HTTPResponse.new_from_socket(sock, - response_body_permitted?) + resp = HTTPResponse.new_from_socket(sock, response_body_permitted?) end while ContinueCode === resp resp end @@ -1042,11 +1067,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 @@ -1075,30 +1100,28 @@ module Net end - } - class HTTP - class Get < ::Net::NetPrivate::HTTPRequest + class Get < HTTPRequest METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true end - class Head < ::Net::NetPrivate::HTTPRequest + class Head < HTTPRequest METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false end - class Post < ::Net::NetPrivate::HTTPRequest + class Post < HTTPRequest METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end - class Put < ::Net::NetPrivate::HTTPRequest + class Put < HTTPRequest METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -1112,11 +1135,9 @@ module Net ### response ### - net_private { - class HTTPResponse < Response - include ::Net::NetPrivate::HTTPHeader + include HTTPHeader CODE_CLASS_TO_OBJ = { '1' => HTTPInformationCode, @@ -1178,7 +1199,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 ) @@ -1250,22 +1271,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 @@ -1273,10 +1294,8 @@ module Net alias body read_body alias entity read_body - private - def terminate read_body end @@ -1305,11 +1324,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 @@ -1326,7 +1345,7 @@ module Net raise ArgumentError, 'both of arg and block are given for HTTP method' end if block then - ::Net::NetPrivate::ReadAdapter.new block + ReadAdapter.new block else dest || '' end @@ -1334,10 +1353,18 @@ module Net end - } + # for backward compatibility - HTTPResponse = NetPrivate::HTTPResponse - HTTPResponseReceiver = NetPrivate::HTTPResponse + HTTPSession = HTTP + + module NetPrivate + HTTPResponse = ::Net::HTTPResponse + HTTPGenericRequest = ::Net::HTTPGenericRequest + HTTPRequest = ::Net::HTTPRequest + Accumulator = ::Net::Accumulator + HTTPHeader = ::Net::HTTPHeader + end + HTTPResponceReceiver = HTTPResponse end # module Net diff --git a/lib/net/pop.rb b/lib/net/pop.rb index 7463f564a9..1dbbc9aeb0 100644 --- a/lib/net/pop.rb +++ b/lib/net/pop.rb @@ -1,6 +1,6 @@ =begin -= net/pop.rb version 1.1.37 += net/pop.rb Copyright (c) 1999-2001 Yukihiro Matsumoto @@ -13,6 +13,8 @@ Ruby Distribute License or GNU General Public License. NOTE: You can find Japanese version of this document in the doc/net directory of the standard ruby interpreter package. +$Id$ + == What is This Module? This module provides your program the functions to retrieve @@ -123,26 +125,19 @@ This example does not create such one. === Using APOP -net/pop also supports APOP authentication. There's two way to use APOP: -(1) using APOP class instead of POP3 -(2) passing true for fifth argument of POP3.start +The net/pop library supports APOP authentication. +To use APOP, use Net::APOP class instead of Net::POP3 class. +You can use utility method, Net::POP3.APOP(). Example: - # (1) require 'net/pop' - Net::APOP.start( 'apop.server.address', 110, - 'YourAccount', 'YourPassword' ) {|pop| - # Rest code is same. - } - # (2) - require 'net/pop' - Net::POP3.start( 'apop.server.address', 110, - 'YourAccount', 'YourPassword', - true #### - ) {|pop| + # use APOP authentication if $isapop == true + pop = Net::POP3.APOP($isapop).new( 'apop.server.address', 110 ) + pop.start( YourAccount', 'YourPassword' ) {|pop| # Rest code is same. } + == Net::POP3 class === Class Methods @@ -162,6 +157,19 @@ net/pop also supports APOP authentication. There's two way to use APOP: end } +: APOP( is_apop ) + returns Net::APOP class object if IS_APOP is true. + returns Net::POP3 class object if false. + Use this method like: + + # example 1 + pop = Net::POP3::APOP($isapop).new( addr, port ) + + # example 2 + Net::POP3::APOP($isapop).start( addr, port ) {|pop| + .... + } + : foreach( address, port = 110, account, password ) {|mail| .... } starts POP3 protocol and iterates for each POPMail object. This method equals to @@ -326,14 +334,17 @@ module Net class POP3 < Protocol - protocol_param :port, '110' - protocol_param :command_type, '::Net::NetPrivate::POP3Command' - protocol_param :apop_command_type, '::Net::NetPrivate::APOPCommand' - - protocol_param :mail_type, '::Net::POPMail' + protocol_param :port, '110' + protocol_param :command_type, '::Net::POP3Command' + protocol_param :apop_command_type, '::Net::APOPCommand' + protocol_param :mail_type, '::Net::POPMail' class << self + def APOP( bool ) + bool ? APOP : POP3 + end + def foreach( address, port = nil, account = nil, password = nil, &block ) start( address, port, account, password ) do |pop| @@ -356,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::NetPrivate::APOPCommand' + protocol_param :command_type, '::Net::APOPCommand' end - APOPSession = APOP - class POPMail @@ -455,7 +476,7 @@ module Net def pop( dest = '', &block ) if block then - dest = NetPrivate::ReadAdapter.new( block ) + dest = ReadAdapter.new( block ) end @command.retr( @num, dest ) end @@ -489,89 +510,84 @@ module Net end - - module NetPrivate - - 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 @@ -591,25 +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 - end # module Net::NetPrivate + # 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 b04c37e4c2..c5c9fa9a91 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -1,6 +1,6 @@ =begin -= net/protocol.rb version 1.1.37 += net/protocol.rb Copyright (c) 1999-2001 Yukihiro Matsumoto @@ -13,6 +13,8 @@ Ruby Distribute License or GNU General Public License. NOTE: You can find Japanese version of this document in the doc/net directory of the standard ruby interpreter package. +$Id$ + =end require 'socket' @@ -21,39 +23,22 @@ require 'timeout' module Net - module NetPrivate - end - - def self.net_private( &block ) - ::Net::NetPrivate.module_eval( &block ) - end - - class Protocol Version = '1.1.37' + Revision = %q$Revision$.split(/\s+/)[1] - class << self - - def start( address, port = nil, *args ) - instance = new( address, port ) - if block_given? then - instance.start( *args ) { yield instance } - else - instance.start( *args ) - instance - end - end + class << self private def protocol_param( name, val ) - module_eval %- - def self.#{name.id2name} - #{val} - end - - + module_eval <<-End, __FILE__, __LINE__ + 1 + def self.#{name.id2name} + #{val} + end + End end end @@ -66,17 +51,30 @@ 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' protocol_param :command_type, 'nil' - protocol_param :socket_type, '::Net::NetPrivate::Socket' + 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 @@ -87,8 +85,8 @@ module Net @active = false - @open_timeout = nil - @read_timeout = nil + @open_timeout = 30 + @read_timeout = 60 @dout = nil end @@ -117,90 +115,80 @@ module Net end # - # open session + # open # def start( *args ) - active? and raise IOError, 'protocol has been opened already' + @active and raise IOError, 'protocol has been opened already' if block_given? then begin - _start args - yield self + do_start( *args ) + @active = true + return yield(self) ensure - finish if active? + finish if @active end - else - _start args end + + do_start( *args ) + @active = true nil end private - def _start( args ) - connect - do_start( *args ) - @active = true - end + # abstract do_start() - def connect - conn_socket @address, @port + def conn_socket + @socket = type.socket_type.open( + conn_address(), conn_port(), + @open_timeout, @read_timeout, @dout ) on_connect - conn_command @socket end - def re_connect + alias conn_address address + alias conn_port port + + def reconn_socket @socket.reopen @open_timeout on_connect end - def conn_socket( addr, port ) - @socket = type.socket_type.open( - addr, port, @open_timeout, @read_timeout, @dout ) - end - - def conn_command( sock ) - @command = type.command_type.new( sock ) + def conn_command + @command = type.command_type.new(@socket) end def on_connect end - def do_start - end - # - # close session + # close # public def finish - active? or raise IOError, 'already closed protocol' - - do_finish if @command and not @command.critical? - disconnect + active? or raise IOError, 'closing already closed protocol' + do_finish @active = false nil end private - def do_finish - @command.quit - end + # abstract do_finish() - def disconnect + def disconn_command + @command.quit if @command and not @command.critical? @command = nil + end + + def disconn_socket if @socket and not @socket.closed? then @socket.close end @socket = nil - on_disconnect - end - - def on_disconnect end end @@ -208,8 +196,6 @@ module Net Session = Protocol - net_private { - class Response def initialize( ctype, code, msg ) @@ -227,13 +213,11 @@ 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 - } - class ProtocolError < StandardError; end class ProtoSyntaxError < ProtocolError; end @@ -265,16 +249,16 @@ module Net class Code def initialize( paren, err ) - @parents = paren + @parents = [self] + paren @err = err - - @parents.push self end - attr_reader :parents + def parents + @parents.dup + end def inspect - "#<#{type}>" + "#<#{type} #{sprintf '0x%x', __id__}>" end def error_type @@ -282,12 +266,12 @@ module Net end def ===( response ) - response.code_type.parents.reverse_each {|i| return true if i == self } + response.code_type.parents.each {|c| return true if c == self } false end def mkchild( err = nil ) - type.new( @parents + [self], err || @err ) + type.new( @parents, err || @err ) end end @@ -306,30 +290,39 @@ module Net - net_private { - class WriteAdapter def initialize( sock, mid ) - @sock = sock + @socket = sock @mid = mid end def inspect - "#<#{type}>" + "#<#{type} socket=#{@socket.inspect}>" + end + + def <<( str ) + @socket.__send__ @mid, str + self end def write( str ) - @sock.__send__ @mid, str + @socket.__send__ @mid, str end - def <<( str ) - @sock.__send__ @mid, str - self + 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 ) @@ -341,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 break - end - end + def call_block( str ) + yield str end end @@ -371,7 +352,7 @@ module Net def initialize( sock ) @socket = sock @last_reply = nil - @critical = false + @atomic = false end attr_accessor :socket @@ -381,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 @@ -407,7 +385,6 @@ module Net check_reply expect end - # # error handle # @@ -415,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 Socket - def initialize( addr, port, otime = nil, rtime = nil, dout = nil ) - @addr = addr - @port = port + class BufferedSocket - @read_timeout = rtime + class << self + alias open new + end + def initialize( addr, port, otime = nil, rtime = nil, dout = nil ) + @address = addr + @port = port + @read_timeout = rtime @debugout = dout - @socket = nil - @sending = '' - @buffer = '' + @socket = nil + @rbuf = nil connect otime D 'opened' end - def connect( otime ) - D "opening connection to #{@addr}..." - timeout( otime ) { - @socket = TCPsocket.new( @addr, @port ) - } - end - private :connect - - attr :pipe, true + attr_reader :address + attr_reader :port - class << self - alias open new + def ip_address + @socket or return '' + @socket.addr[3] end - def inspect - "#<#{type} #{closed? ? 'closed' : 'opened'}>" - end + attr_reader :socket - def reopen( otime = nil ) - D 'reopening...' - close - connect otime - D 'reopened' + def connect( otime ) + D "opening connection to #{@address}..." + timeout( otime ) { + @socket = TCPsocket.new( @address, @port ) + } + @rbuf = '' end - - attr :socket, true + private :connect def close if @socket then @@ -498,51 +472,46 @@ module Net D 'close call for already closed socket' end @socket = nil - @buffer = '' + @rbuf = '' end - def closed? - not @socket + def reopen( otime = nil ) + D 'reopening...' + close + connect otime + D 'reopened' end - def address - @addr.dup + def closed? + not @socket end - alias addr address - - attr_reader :port - - def ip_address - @socket or return '' - @socket.addr[3] + def inspect + "#<#{type} #{closed? ? 'closed' : 'opened'}>" end - alias ipaddr ip_address - - attr_reader :sending - + ### + ### READ + ### # - # read + # 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 + @buffer.size < len do - rsize += rbuf_moveto( dest, @buffer.size ) + while rsize + @rbuf.size < len do + 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" @@ -555,7 +524,7 @@ module Net rsize = 0 begin while true do - rsize += rbuf_moveto( dest, @buffer.size ) + rsize += rbuf_moveto(dest, @rbuf.size) rbuf_fill end rescue EOFError @@ -566,28 +535,34 @@ module Net dest end - def readuntil( target, igneof = false ) + def readuntil( target, ignore = false ) dest = '' begin while true do - idx = @buffer.index( target ) + idx = @rbuf.index(target) break if idx rbuf_fill end rbuf_moveto dest, idx + target.size rescue EOFError - raise unless igneof - rbuf_moveto dest, @buffer.size + 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...' @@ -601,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...' @@ -617,17 +592,19 @@ module Net # D_on "read #{i} items" end + # + # lib (reader) + # private - - READ_SIZE = 1024 * 4 + BLOCK_SIZE = 1024 * 2 def rbuf_fill - unless IO.select [@socket], nil, nil, @read_timeout then + until IO.select [@socket], nil, nil, @read_timeout do on_read_timeout end - @buffer << @socket.sysread( READ_SIZE ) + @rbuf << @socket.sysread(BLOCK_SIZE) end def on_read_timeout @@ -635,65 +612,69 @@ module Net end def rbuf_moveto( dest, len ) - bsi = @buffer.size - s = @buffer[ 0, len ] - dest << s - @buffer = @buffer[ len, bsi - len ] - - @debugout << %<read "#{Net.quote s}"\n> if @debugout + dest << (s = @rbuf.slice!(0, len)) + @debugout << %Q[-> #{s.dump}\n] if @debugout len end + ### + ### WRITE + ### + # - # write interfece + # 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 ::Net::NetPrivate::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 = use_each_crlf_line { - if block then - block.call ::Net::NetPrivate::WriteAdapter.new( self, :wpend_in ) - else - wpend_in src - end + wsize = using_each_crlf_line { + if block_given? then + yield WriteAdapter.new(self, :wpend_in) + else + wpend_in src + end } D_on "wrote #{wsize} bytes text" wsize end - private - def wpend_in( src ) line = nil pre = @writtensize @@ -705,24 +686,24 @@ module Net @writtensize - pre end - def use_each_crlf_line + def using_each_crlf_line writing { - @wbuf = '' + @wbuf = '' - yield + yield - if not @wbuf.empty? then # un-terminated 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 @@ -759,7 +740,7 @@ module Net when File while true do - i = src.read( 2048 ) + i = src.read(2048) break unless i i[0,0] = @wbuf @wbuf = i @@ -777,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 @@ -824,14 +803,14 @@ module Net end - } - - def Net.quote( str ) - str = str.gsub( "\n", '\\n' ) - str.gsub!( "\r", '\\r' ) - str.gsub!( "\t", '\\t' ) - str + # for backward compatibility + module NetPrivate + Response = ::Net::Response + WriteAdapter = ::Net::WriteAdapter + ReadAdapter = ::Net::ReadAdapter + Command = ::Net::Command + Socket = ::Net::BufferedSocket end end # module Net diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb index 04f101d8b7..363fc39088 100644 --- a/lib/net/smtp.rb +++ b/lib/net/smtp.rb @@ -1,6 +1,6 @@ =begin -= net/smtp.rb version 1.1.37 += net/smtp.rb Copyright (c) 1999-2001 Yukihiro Matsumoto @@ -13,6 +13,8 @@ Ruby Distribute License or GNU General Public License. NOTE: You can find Japanese version of this document in the doc/net directory of the standard ruby interpreter package. +$Id$ + == What is This Module? This module provides your program the functions to send internet @@ -90,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 @@ -109,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) @@ -177,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 @@ -186,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 @@ -213,12 +218,10 @@ require 'digest/md5' module Net - class SMTP < Protocol protocol_param :port, '25' - protocol_param :command_type, '::Net::NetPrivate::SMTPCommand' - + protocol_param :command_type, '::Net::SMTPCommand' def initialize( addr, port = nil ) super @@ -227,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 @@ -281,113 +258,133 @@ module Net raise ArgumentError, 'both of account and password are required' mid = 'auth_' + (authtype || 'cram_md5').to_s - @command.respond_to? mid or + command().respond_to? mid or raise ArgumentError, "wrong auth type #{authtype.to_s}" - @command.__send__ mid, user, secret + command().__send__ mid, user, secret end end - end + def do_finish + disconn_command + disconn_socket + end - SMTPSession = SMTP + # + # SMTP operations + # + public - module NetPrivate + def send_mail( mailsrc, from_addr, *to_addrs ) + do_ready from_addr, to_addrs.flatten + command().write_mail mailsrc, nil + end + + alias sendmail send_mail + + def ready( from_addr, *to_addrs, &block ) + do_ready from_addr, to_addrs.flatten + command().write_mail nil, block + end + + private + + def do_ready( from_addr, to_addrs ) + if to_addrs.empty? then + raise ArgumentError, 'mail destination does not given' + end + command().mailfrom from_addr + command().rcpt to_addrs + command().data + end + + end class SMTPCommand < Command def initialize( sock ) super - critical { - check_reply SuccessCode + atomic { + check_reply SuccessCode } end - - def helo( fromdom ) - critical { - getok sprintf( 'HELO %s', fromdom ) + def helo( domain ) + atomic { + getok sprintf('HELO %s', domain) } end - - def ehlo( fromdom ) - critical { - getok sprintf( 'EHLO %s', fromdom ) + def ehlo( domain ) + atomic { + getok sprintf('EHLO %s', domain) } end - # "PLAIN" authentication [RFC2554] def auth_plain( user, secret ) - critical { - getok sprintf( 'AUTH PLAIN %s', - ["\0#{user}\0#{secret}"].pack('m').chomp ) + atomic { + getok sprintf('AUTH PLAIN %s', + ["\0#{user}\0#{secret}"].pack('m').chomp) } end # "CRAM-MD5" authentication [RFC2195] def auth_cram_md5( user, secret ) - critical { - rep = getok( 'AUTH CRAM-MD5', ContinueCode ) - challenge = rep.msg.split(' ')[1].unpack('m')[0] - secret = Digest::MD5.digest( secret ) if secret.size > 64 - - isecret = secret + "\0" * (64 - secret.size) - osecret = isecret.dup - 0.upto( 63 ) do |i| - isecret[i] ^= 0x36 - osecret[i] ^= 0x5c - end - tmp = Digest::MD5.digest( isecret + challenge ) - tmp = Digest::MD5.hexdigest( osecret + tmp ) - - getok [user + ' ' + tmp].pack('m').chomp + atomic { + rep = getok( 'AUTH CRAM-MD5', ContinueCode ) + challenge = rep.msg.split(' ')[1].unpack('m')[0] + secret = Digest::MD5.digest(secret) if secret.size > 64 + + isecret = secret + "\0" * (64 - secret.size) + osecret = isecret.dup + 0.upto( 63 ) do |i| + isecret[i] ^= 0x36 + osecret[i] ^= 0x5c + end + tmp = Digest::MD5.digest( isecret + challenge ) + tmp = Digest::MD5.hexdigest( osecret + tmp ) + + getok [user + ' ' + tmp].pack('m').chomp } end - def mailfrom( fromaddr ) - critical { - getok sprintf( 'MAIL FROM:<%s>', fromaddr ) + atomic { + getok sprintf('MAIL FROM:<%s>', fromaddr) } end - def rcpt( toaddrs ) toaddrs.each do |i| - critical { - getok sprintf( 'RCPT TO:<%s>', i ) + atomic { + getok sprintf('RCPT TO:<%s>', i) } end end - def data - return unless begin_critical + return unless begin_atomic getok 'DATA', ContinueCode end def write_mail( mailsrc, block ) - @socket.write_pendstr mailsrc, block + @socket.write_pendstr mailsrc, &block check_reply SuccessCode - end_critical + end_atomic end - def quit - critical { - getok 'QUIT' + atomic { + getok 'QUIT' } end - private - def get_reply arr = read_reply stat = arr[0][0,3] @@ -408,7 +405,6 @@ module Net Response.new( klass, stat, arr.join('') ) end - def read_reply arr = [] while true do @@ -424,6 +420,12 @@ module Net end - end # module Net::NetPrivate + # for backward compatibility + + SMTPSession = SMTP + + module NetPrivate + SMTPCommand = ::Net::SMTPCommand + end end # module Net |