diff options
Diffstat (limited to 'lib/net')
-rw-r--r-- | lib/net/http.rb | 1118 | ||||
-rw-r--r-- | lib/net/imap.rb | 487 | ||||
-rw-r--r-- | lib/net/pop.rb | 13 | ||||
-rw-r--r-- | lib/net/protocol.rb | 309 | ||||
-rw-r--r-- | lib/net/smtp.rb | 29 | ||||
-rw-r--r-- | lib/net/telnet.rb | 217 |
6 files changed, 1355 insertions, 818 deletions
diff --git a/lib/net/http.rb b/lib/net/http.rb index 3900ed6c68..26e5285525 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1,8 +1,9 @@ =begin -= net/http.rb version 1.1.32 += net/http.rb version 1.1.34 + +written by Minero Aoki <aamine@dp.u-netsurf.ne.jp> -maintained by Minero Aoki <aamine@dp.u-netsurf.ne.jp> This file is derived from "http-access.rb". This program is free software. @@ -14,9 +15,34 @@ You can get it from RAA (Ruby Application Archive: http://www.ruby-lang.org/en/raa.html). -= class HTTP +== http.rb version 1.2 features + +You can use 1.2 features by calling HTTP.version_1_2. And +calling Net::HTTP.version_1_1 allows to use 1.1 features. + + # example + HTTP.start {|http1| ...(http1 has 1.1 features)... } + + HTTP.version_1_2 + HTTP.start {|http2| ...(http2 has 1.2 features)... } + + HTTP.version_1_1 + HTTP.start {|http3| ...(http3 has 1.1 features)... } + +Changes are: -== Class Methods + * HTTP#get, head, post does not raise ProtocolError + * HTTP#get, head, post returns only one object, a HTTPResponse object + * HTTPResponseReceiver is joined into HTTPResponse + * request object: HTTP::Get, Head, Post; and HTTP#request(req) + +WARNING: These features are not definite yet. +They will change without notice! + + +== class HTTP + +=== Class Methods : new( address = 'localhost', port = 80, proxy_addr = nil, proxy_port = nil ) creates a new Net::HTTP object. @@ -49,7 +75,7 @@ You can get it from RAA HTTP default port (80). -== Methods +=== Methods : start : start {|http| .... } @@ -72,7 +98,7 @@ You can get it from RAA get data from "path" on connecting host. "header" must be a Hash like { 'Accept' => '*/*', ... }. Data is written to "dest" by using "<<" method. - This method returns Net::HTTPResponse object, and "dest". + This method returns HTTPResponse object, and "dest". # example response, body = http.get( '/index.html' ) @@ -95,7 +121,7 @@ You can get it from RAA : head( path, header = nil ) gets only header from "path" on connecting host. "header" is a Hash like { 'Accept' => '*/*', ... }. - This method returns a Net::HTTPResponse object. + This method returns a HTTPResponse object. You can http header from this object like: response['content-length'] #-> '2554' @@ -109,7 +135,7 @@ You can get it from RAA If the body exists, also gets entity body. Data is written to "dest" by using "<<" method. "header" must be a Hash like { 'Accept' => '*/*', ... }. - This method returns Net::HTTPResponse object and "dest". + This method returns HTTPResponse object and "dest". If called with block, gives a part of entity body string. @@ -181,21 +207,21 @@ You can get it from RAA response.body -= class HTTPResponse +== class HTTPResponse HTTP response object. All "key" is case-insensitive. -== Methods +=== Methods : body - the entity body. ("dest" argument for HTTP#get, post, put) + the entity body (String). : self[ key ] returns header field for "key". for HTTP, value is a string like 'text/plain'(for Content-Type), - '2045'(for Content-Length), 'bytes 0-1024/10024'(for Content-Range). - Multiple header had be joined by HTTP1.1 scheme. + '2045'(for Content-Length), 'bytes 0-1023/10024'(for Content-Range). + If there's some fields which has same name, they are joined with ','. : self[ key ] = val set field value for "key". @@ -204,88 +230,92 @@ All "key" is case-insensitive. true if key exists : each {|name,value| .... } - iterates for each field name and value pair + iterates for each field name and value pair. : code - HTTP result code string. For example, '302' + HTTP result code string. For example, '302'. : message - HTTP result message. For example, 'Not Found' + HTTP result message. For example, 'Not Found'. -= class HTTPResponseReceiver +== class HTTPResponseReceiver -== Methods +=== Methods : header : response - Net::HTTPResponse object + HTTPResponse object -: body( dest = '' ) -: entity( dest = '' ) - entity body. A body is written to "dest" using "<<" method. +: read_body( dest = '' ) + reads entity body into DEST by calling "<<" method and + returns DEST. -: body {|str| ... } - gets entity body with block. - If this method is called twice, block is not executed and - returns first "dest". +: read_body {|string| ... } + reads entity body little by little and gives it to block + until entity ends. +: body +: entity + entity body. If #read_body is called already, returns its + argument DEST. Else returns entity body as String. -= http.rb version 1.2 features + Calling this method any times causes returning same + object (does not read entity again). -You can use 1.2 features by calling HTTP.version_1_2. And -calling Net::HTTP.version_1_1 allows to use 1.1 features. +=end - # example - HTTP.start {|http1| ...(http1 has 1.1 features)... } +require 'net/protocol' - HTTP.version_1_2 - HTTP.start {|http2| ...(http2 has 1.2 features)... } - HTTP.version_1_1 - HTTP.start {|http3| ...(http3 has 1.1 features)... } +module Net -== Method (only diff to 1.1) + class HTTPBadResponse < StandardError; end + class HTTPHeaderSyntaxError < StandardError; end -: get( path, u_header = nil ) -: get( path, u_header = nil ) {|str| .... } - gets document from "path". - returns HTTPResponse object. -: head( path, u_header = nil ) - gets only document header from "path". - returns HTTPResponse object. + class HTTP < Protocol -: post( path, data, u_header = nil ) -: post( path, data, u_header = nil ) {|str| .... } - posts "data" to "path" entity and gets document. - returns HTTPResponse object. + HTTPVersion = '1.1' -=end + # + # connection + # -require 'net/protocol' + protocol_param :port, '80' -module Net + def initialize( addr = nil, port = nil ) + super - class HTTPBadResponse < StandardError; end + @proxy_address = nil + @proxy_port = nil + @curr_http_version = HTTPVersion + @seems_1_0_server = false + end - class HTTP < Protocol - protocol_param :port, '80' - protocol_param :command_type, '::Net::NetPrivate::HTTPCommand' + private + def conn_command( sock ) + end + + def do_finish + end + + + # + # proxy + # + + public - ### - ### proxy - ### class << self def Proxy( p_addr, p_port = nil ) - ::Net::NetPrivate::HTTPProxy.create_proxy_class( - p_addr, p_port || self.port ) + ProxyMod.create_proxy_class( p_addr, p_port || self.port ) end alias orig_new new @@ -293,7 +323,7 @@ module Net def new( address = nil, port = nil, p_addr = nil, p_port = nil ) c = p_addr ? self::Proxy(p_addr, p_port) : self i = c.orig_new( address, port ) - setimplv i + setvar i i end @@ -332,385 +362,230 @@ module Net end - ### - ### 1.2 implementation - ### + module ProxyMod - @@newimpl = false + class << self - #class << self + def create_proxy_class( p_addr, p_port ) + mod = self + klass = Class.new( HTTP ) + klass.module_eval { + include mod + @proxy_address = p_addr + @proxy_port = p_port + } + def klass.proxy_class? + true + end - def self.version_1_2 - @@newimpl = true - end + def klass.proxy_address + @proxy_address + end - def self.version_1_1 - @@newimpl = false - end + def klass.proxy_port + @proxy_port + end - #private + klass + end - def self.setimplv( obj ) - f = @@newimpl - obj.instance_eval { @newimpl = f } end - #end - - - ### - ### http operations - ### - - def get( path, u_header = nil, dest = nil, &block ) - resp = get2( path, u_header ) {|f| f.body( dest, &block ) } - if @newimpl then - resp - else - resp.value - return resp, resp.body + def initialize( addr, port ) + super + @proxy_address = type.proxy_address + @proxy_port = type.proxy_port end - end - - def get2( path, u_header = nil, &block ) - common_oper( u_header, true, block ) {|uh| - @command.get edit_path(path), uh - } - end + + attr_reader :proxy_address, :proxy_port + alias proxyaddr proxy_address + alias proxyport proxy_port - def head( path, u_header = nil ) - resp = head2( path, u_header ) - unless @newimpl then - resp.value + def proxy? + true end - resp - end - - def head2( path, u_header = nil, &block ) - common_oper( u_header, false, block ) {|uh| - @command.head edit_path(path), uh - } - end - - - def post( path, data, u_header = nil, dest = nil, &block ) - resp = post2( path, data, u_header ) {|f| f.body( dest, &block ) } - if @newimpl then - resp - else - resp.value - return resp, resp.body + + private + + def conn_socket( addr, port ) + super @proxy_address, @proxy_port end - end - - def post2( path, data, u_header = nil, &block ) - common_oper( u_header, true, block ) {|uh| - @command.post edit_path(path), uh, data - } - end - - # not tested because I could not setup apache (__;;; - def put( path, src, u_header = nil ) - resp = put2( path, src, u_header ) {|f| f.body } - if @newimpl then - resp - else - resp.value - return resp, resp.body + def edit_path( path ) + 'http://' + addr_port + path end - end + + end # module ProxyMod - def put2( path, src, u_header = nil, &block ) - common_oper( u_header, true, block ) {|uh| - @command.put path, uh, src - } - end - - - private - - - def common_oper( u_header, body_exist, block ) - header = procheader( u_header ) - recv = err = nil - - connecting( header ) { - recv = HTTPResponseReceiver.new( @command, body_exist ) - yield header - begin - block.call recv if block - rescue Exception => err - ; - end - recv.terminate - - recv.response - } - raise err if err - recv.response - end + # + # for backward compatibility + # - def connecting( header ) - if not @socket then - header['Connection'] = 'close' - start - elsif @socket.closed? then - @socket.reopen - end + @@newimpl = false - resp = yield + class << self - unless keep_alive? header, resp then - @socket.close + def version_1_2 + @@newimpl = true end - end - def keep_alive?( header, resp ) - if resp.key? 'connection' then - if /keep-alive/i === resp['connection'] then - return true - end - elsif resp.key? 'proxy-connection' then - if /keep-alive/i === resp['proxy-connection'] then - return true - end - elsif header.key? 'Connection' then - if /keep-alive/i === header['Connection'] then - return true - end - else - if @command.http_version == '1.1' then - return true - end + def version_1_1 + @@newimpl = false end - false - end - - def procheader( h ) - ret = {} - ret[ 'Host' ] = address + - ((port == HTTP.port) ? '' : ":#{port}") - ret[ 'Connection' ] = 'Keep-Alive' - ret[ 'Accept' ] = '*/*' + private - return ret unless h - tmp = {} - h.each do |k,v| - key = k.split('-').collect {|i| i.capitalize }.join('-') - if tmp[key] then - $stderr.puts "'#{key}' http header appered twice" if $VERBOSE - end - tmp[key] = v + def setvar( obj ) + f = @@newimpl + obj.instance_eval { @newimpl = f } end - ret.update tmp - ret end - def do_finish - end + # + # http operations + # - end - - HTTPSession = HTTP - - - module NetPrivate - - module HTTPProxy - - class << self - - def create_proxy_class( p_addr, p_port ) - klass = Class.new( HTTP ) - klass.module_eval { - include HTTPProxy - @proxy_address = p_addr - @proxy_port = p_port - } - def klass.proxy_class? - true - end + public - def klass.proxy_address - @proxy_address + def self.def_http_method( 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 klass.proxy_port - @proxy_port + def #{name}2( path, #{hasdata ? 'data,' : ''} + u_header = nil, &block ) + request( #{cname}.new(path, u_header), + #{hasdata ? 'data,' : ''} &block ) end - - klass - end - - end - - - def initialize( addr, port ) - super - @proxy_address = type.proxy_address - @proxy_port = type.proxy_port + ---- + module_eval src, __FILE__, lineno end - attr_reader :proxy_address, :proxy_port + def_http_method :Get, true, false + def_http_method :Head, false, false + def_http_method :Post, true, true + def_http_method :Put, false, true - alias proxyaddr proxy_address - alias proxyport proxy_port - - def proxy? - true - end - - def connect( addr = nil, port = nil ) - super @proxy_address, @proxy_port - end - - def edit_path( path ) - 'http://' + address + (port == type.port ? '' : ":#{port}") + path + def request( req, *args ) + common_oper( req ) { + req.__send__( :exec, + @socket, @curr_http_version, edit_path(req.path), *args ) + yield req.response if block_given? + } + req.response end - - end - - end # net private + private - class HTTPResponseReceiver - - def initialize( command, body_exist ) - @command = command - @body_exist = body_exist - @header = @body = nil - end - - def inspect - "#<#{type}>" - end - def read_header - unless @header then - stream_check - @header = @command.get_response + def common_oper( req ) + req['connection'] ||= 'keep-alive' + if not @socket then + start + req['connection'] = 'close' + elsif @socket.closed? then + re_connect end - @header - end - - alias header read_header - alias response read_header - - def read_body( dest = nil, &block ) - unless @body then - read_header - - to = procdest( dest, block ) - stream_check - - if @body_exist and @header.code_type.body_exist? then - @command.get_body @header, to - @header.body = @body = to - else - @command.no_body - @header.body = nil - @body = 1 - end + if not req.body_exist? or @seems_1_0_server then + req['connection'] = 'close' end - @body == 1 ? nil : @body - end - - alias body read_body - alias entity read_body + req['host'] = addr_port - def terminate - read_header - read_body - @command = nil - end + yield req + req.response.__send__ :terminate + @curr_http_version = req.response.http_version - - private - - def stream_check - unless @command then - raise IOError, 'receiver was used out of block' - end - end - - def procdest( dest, block ) - if dest and block then - raise ArgumentError, - 'both of arg and block are given for HTTP method' - end - if block then - NetPrivate::ReadAdapter.new block + if not req.response.body then + @socket.close + elsif keep_alive? req, req.response then + D 'Conn keep-alive' + if @socket.closed? then # (only) read stream had been closed + D 'Conn (but seems 1.0 server)' + @seems_1_0_server = true + @socket.close + end else - dest or '' + D 'Conn close' + @socket.close end - end - - end - - HTTPReadAdapter = HTTPResponseReceiver - - class HTTPResponse < Response - - def initialize( code_type, code, msg ) - super - @data = {} - @body = nil + req.response end - attr_accessor :body + def keep_alive?( req, res ) + /close/i === req['connection'].to_s and return false + @seems_1_0_server and return false - def inspect - "#<#{type.name} #{code}>" - end + /keep-alive/i === res['connection'].to_s and return true + /close/i === res['connection'].to_s and return false + /keep-alive/i === res['proxy-connection'].to_s and return true + /close/i === res['proxy-connection'].to_s and return false - def []( key ) - @data[ key.downcase ] + @curr_http_version == '1.1' and return true + false end - def []=( key, val ) - @data[ key.downcase ] = val - end - def each( &block ) - @data.each( &block ) - end + # + # utils + # - def each_key( &block ) - @data.each_key( &block ) - end + public - def each_value( &block ) - @data.each_value( &block ) + 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 delete( key ) - @data.delete key.downcase + def self.get_print( addr, path, port = nil ) + print get( addr, path, port ) end - def key?( key ) - @data.key? key.downcase - end - def to_hash - @data.dup + private + + def addr_port + address + (port == HTTP.port ? '' : ":#{port}") end - def value - unless SuccessCode === self then - error! self + def D( msg ) + if @dout then + @dout << msg + @dout << "\n" end end end + HTTPSession = HTTP + + class Code @@ -776,101 +651,246 @@ module Net HTTPVersionNotSupported = HTTPServerErrorCode.http_mkchild - module NetPrivate + ### + ### header + ### - class HTTPCommand < Command + net_private { - HTTPVersion = '1.1' + module HTTPHeader - def initialize( sock ) - @http_version = HTTPVersion - super sock + def size + @header.size end - attr_reader :http_version + alias length size - def inspect - "#<Net::HTTPCommand>" + def []( key ) + @header[ key.downcase ] end + def []=( key, val ) + @header[ key.downcase ] = val + end - ### - ### request - ### + def each( &block ) + @header.each( &block ) + end - public + def each_key( &block ) + @header.each_key( &block ) + end - def get( path, u_header ) - return unless begin_critical - request sprintf('GET %s HTTP/%s', path, HTTPVersion), u_header + def each_value( &block ) + @header.each_value( &block ) end - - def head( path, u_header ) - return unless begin_critical - request sprintf('HEAD %s HTTP/%s', path, HTTPVersion), u_header + + def delete( key ) + @header.delete key.downcase + end + + def key?( key ) + @header.key? key.downcase + end + + def to_hash + @header.dup end - def post( path, u_header, data ) - return unless begin_critical - u_header[ 'Content-Length' ] = data.size.to_s - request sprintf('POST %s HTTP/%s', path, HTTPVersion), u_header - @socket.write data + def canonical_each + @header.each do |k,v| + yield canonical(k), v + end end - def put( path, u_header, src ) - return unless begin_critical - request sprintf('PUT %s HTTP/%s', path, HTTPVersion), u_header - @socket.write_bin src + def canonical( k ) + k.split('-').collect {|i| i.capitalize }.join('-') end - # def delete + def range + s = @header['range'] + s or return nil - # def trace + arr = [] + s.split(',').each do |spec| + m = /bytes\s*=\s*(\d+)?\s*-\s*(\d+)?/i.match( spec ) + m or raise HTTPHeaderSyntaxError, "wrong Range: #{spec}" - # def options + d1 = m[1].to_i + d2 = m[2].to_i + if m[1] and m[2] then arr.push (d1 .. d2) + elsif m[1] then arr.push (d1 .. -1) + elsif m[2] then arr.push (-d2 .. -1) + else + raise HTTPHeaderSyntaxError, 'range is not specified' + end + end - def quit + return *arr end + def range=( r, fin = nil ) + if fin then + r = r ... r+fin + end - private + case r + when Numeric + s = r > 0 ? "0-#{r - 1}" : "-#{-r}" + when Range + first = r.first + last = r.last + if r.exclude_end? then + last -= 1 + end - def request( req, u_header ) - @socket.writeline req - u_header.each do |n,v| - @socket.writeline n + ': ' + v + if last == -1 then + s = first > 0 ? "#{first}-" : "-#{-first}" + else + first >= 0 or raise HTTPHeaderSyntaxError, 'range.first is negative' + last > 0 or raise HTTPHeaderSyntaxError, 'range.last is negative' + first < last or raise HTTPHeaderSyntaxError, 'must be .first < .last' + s = "#{first}-#{last}" + end + else + raise TypeError, 'Range/Integer is required' end - @socket.writeline '' + + @header['range'] = "bytes=#{s}" + r end + alias set_range range= - ### - ### response line & header - ### + def content_length + s = @header['content-length'] + s or return nil - public + m = /\d+/.match(s) + m or raise HTTPHeaderSyntaxError, 'wrong Content-Length format' + m[0].to_i + end - def get_response - resp = get_resp0 - resp = get_resp0 while ContinueCode === resp - resp + def chunked? + s = @header['transfer-encoding'] + (s and /(?:\A|[^\-\w])chunked(?:[^\-\w]|\z)/i === s) ? true : false + end + + def content_range + s = @header['content-range'] + s or return nil + + m = %r<bytes\s+(\d+)-(\d+)/(?:\d+|\*)>i.match( s ) + m or raise HTTPHeaderSyntaxError, 'wrong Content-Range format' + + m[1].to_i .. m[2].to_i + 1 + end + + def range_length + r = content_range + r and r.length + end + + def basic_auth( acc, pass ) + @header['authorization'] = + 'Basic ' + ["#{acc}:#{pass}"].pack('m').gsub(/\s+/, '') + end + + end + + } + + + ### + ### request + ### + + net_private { + + class HTTPRequest + + include ::Net::NetPrivate::HTTPHeader + + def initialize( path, uhead = nil ) + @path = path + @header = tmp = {} + return unless uhead + uhead.each do |k,v| + key = k.downcase + if tmp.key? key then + $stderr.puts "WARNING: duplicated HTTP header: #{k}" if $VERBOSE + end + tmp[ key ] = v.strip + end + tmp['accept'] ||= '*/*' + + @socket = nil + @response = nil + end + + attr_reader :path + attr_reader :response + + def inspect + "\#<#{type}>" + end + + def body_exist? + type::HAS_BODY end private - def get_resp0 - resp = get_reply + # + # write + # + + def exec( sock, ver, path ) + ready( sock ) { + request ver, path + } + @response + end + + def ready( sock ) + @response = nil + @socket = sock + yield + @response = get_response + @socket = nil + end + + def request( ver, path ) + @socket.writeline sprintf('%s %s HTTP/%s', type::METHOD, path, ver) + canonical_each do |k,v| + @socket.writeline k + ': ' + v + end + @socket.writeline '' + end + + # + # read + # + + def get_response + begin + resp = read_response + end while ContinueCode === resp + resp + end + + def read_response + resp = get_resline while true do - line = @socket.readline + line = @socket.readuntil( "\n", true ) # ignore EOF + line.sub!( /\s+\z/, '' ) # don't use chop! break if line.empty? m = /\A([^:]+):\s*/.match( line ) - unless m then - raise HTTPBadResponse, 'wrong header line format' - end + m or raise HTTPBadResponse, 'wrong header line format' nm = m[1] line = m.post_match if resp.key? nm then @@ -883,23 +903,117 @@ module Net resp end - def get_reply + def get_resline str = @socket.readline - m = /\AHTTP\/(\d+\.\d+)?\s+(\d\d\d)\s*(.*)\z/i.match( str ) - unless m then - raise HTTPBadResponse, "wrong status line: #{str}" - end - @http_version = m[1] + m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/i.match( str ) + m or raise HTTPBadResponse, "wrong status line: #{str}" + httpver = m[1] status = m[2] discrip = m[3] - code = HTTPCODE_TO_OBJ[status] || - HTTPCODE_CLASS_TO_OBJ[status[0,1]] || - UnknownCode - HTTPResponse.new( code, status, discrip ) + ::Net::NetPrivate::HTTPResponse.new( + status, discrip, @socket, type::HAS_BODY, httpver ) + end + + end + + + class HTTPRequestWithBody < HTTPRequest + + private + + def exec( sock, ver, path, str = nil ) + check_arg str, block_given? + + if block_given? then + ac = Accumulator.new + yield ac # must be yield, DO NOT USE block.call + data = ac.terminate + else + data = str + end + @header['content-length'] = data.size.to_s + @header.delete 'transfer-encoding' + + ready( sock ) { + request ver, path + @socket.write data + } + @response + end + + def check_arg( data, blkp ) + if data and blkp then + raise ArgumentError, 'both of data and block given' + end + unless data or blkp then + raise ArgumentError, 'str or block required' + end + end + + end + + + class Accumulator + + def initialize + @buf = '' + end + + def write( s ) + @buf.concat s + end + + alias << write + + def terminate + ret = @buf + @buf = nil + ret + end + + end + + } + + + class HTTP + + class Get < ::Net::NetPrivate::HTTPRequest + HAS_BODY = true + METHOD = 'GET' + end + + class Head < ::Net::NetPrivate::HTTPRequest + HAS_BODY = false + METHOD = 'HEAD' + end + + class Post < ::Net::NetPrivate::HTTPRequestWithBody + HAS_BODY = true + METHOD = 'POST' + end + + class Put < ::Net::NetPrivate::HTTPRequestWithBody + HAS_BODY = true + METHOD = 'PUT' end - HTTPCODE_CLASS_TO_OBJ = { + end + + + + ### + ### response + ### + + net_private { + + class HTTPResponse < Response + + include ::Net::NetPrivate::HTTPHeader + + CODE_CLASS_TO_OBJ = { '1' => HTTPInformationCode, '2' => HTTPSuccessCode, '3' => HTTPRedirectionCode, @@ -907,7 +1021,7 @@ module Net '5' => HTTPServerErrorCode } - HTTPCODE_TO_OBJ = { + CODE_TO_OBJ = { '100' => ContinueCode, '101' => HTTPSwitchProtocol, @@ -951,22 +1065,87 @@ module Net '505' => HTTPVersionNotSupported } + def initialize( stat, msg, sock, be, hv ) + code = CODE_TO_OBJ[stat] || + CODE_CLASS_TO_OBJ[stat[0,1]] || + UnknownCode + super code, stat, msg + @socket = sock + @body_exist = be + @http_version = hv - ### - ### body - ### + @header = {} + @body = nil + @read = false + end - public + attr_reader :http_version + + def inspect + "#<#{type} #{code}>" + end + + def value + SuccessCode === self or error! self + end - def get_body( resp, dest ) - if chunked? resp then + + # + # header (for backward compatibility) + # + + def read_header + self + end + + alias header read_header + alias response read_header + + # + # body + # + + def read_body( dest = nil, &block ) + if @read and (dest or block) then + raise IOError, "#{type}\#read_body called twice with argument" + end + + unless @read then + 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 + end + + @body + end + + alias body read_body + alias entity read_body + + + private + + + def terminate + read_body + end + + def read_body_0( dest ) + if chunked? then read_chunked dest else - clen = content_length( resp ) + clen = content_length if clen then - @socket.read clen, dest + @socket.read clen, dest, true # ignore EOF else - clen = range_length( resp ) + clen = range_length if clen then @socket.read clen, dest else @@ -974,16 +1153,8 @@ module Net end end end - end_critical - end - - def no_body - end_critical end - - private - def read_chunked( dest ) len = nil total = 0 @@ -991,9 +1162,7 @@ module Net while true do line = @socket.readline m = /[0-9a-fA-F]+/.match( line ) - unless m then - raise HTTPBadResponse, "wrong chunk size line: #{line}" - end + m or raise HTTPBadResponse, "wrong chunk size line: #{line}" len = m[0].hex break if len == 0 @socket.read( len, dest ); total += len @@ -1004,44 +1173,27 @@ module Net end end - def content_length( resp ) - if resp.key? 'content-length' then - m = /\d+/.match( resp['content-length'] ) - unless m then - raise HTTPBadResponse, 'wrong Content-Length format' - end - m[0].to_i - else - nil - end + def stream_check + @socket.closed? and raise IOError, 'try to read body out of block' end - def chunked?( resp ) - tmp = resp['transfer-encoding'] - tmp and /(?:\A|\s+)chunked(?:\s+|\z)/i === tmp - end - - def range_length( resp ) - if resp.key? 'content-range' then - m = %r<bytes\s+(\d+)-(\d+)/\d+>.match( resp['content-range'] ) - unless m then - raise HTTPBadResponse, 'wrong Content-Range format' - end - l = m[2].to_i - u = m[1].to_i - if l > u then - nil - else - u - l - end + def procdest( dest, block ) + if dest and block then + raise ArgumentError, 'both of arg and block are given for HTTP method' + end + if block then + ::Net::NetPrivate::ReadAdapter.new block else - nil + dest || '' end end end + } + - end # module Net::NetPrivate + HTTPResponse = NetPrivate::HTTPResponse + HTTPResponseReceiver = NetPrivate::HTTPResponse end # module Net diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 34d324ed12..ea064d2552 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -7,9 +7,10 @@ Copyright (C) 2000 Shugo Maeda <shugo@ruby-lang.org> This library is distributed under the terms of the Ruby license. You can freely distribute/modify this library. -== class Net::IMAP +== Net::IMAP Net::IMAP implements Internet Message Access Protocol (IMAP) clients. +(The protocol is described in ((<[IMAP]>)).) === Super Class @@ -102,21 +103,24 @@ Object : list(refname, mailbox) Sends a LIST command, and returns a subset of names from the complete set of all names available to the client. + The return value is an array of ((<Net::IMAP::MailboxList>)). ex). imap.create("foo/bar") imap.create("foo/baz") p imap.list("", "foo/%") - #=> [#<Net::IMAP::MailboxList attr=[:NoSelect], delim="/", name="foo/">, #<Net::IMAP::MailboxList attr=[:NoInferiors, :Marked], delim="/", name="foo/bar">, #<Net::IMAP::MailboxList attr=[:NoInferiors], delim="/", name="foo/baz">] + #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">] : lsub(refname, mailbox) Sends a LSUB command, and returns a subset of names from the set of names that the user has declared as being "active" or "subscribed". + The return value is an array of ((<Net::IMAP::MailboxList>)). : status(mailbox, attr) Sends a STATUS command, and returns the status of the indicated mailbox. + The return value is a hash of attributes. ex). p imap.status("inbox", ["MESSAGES", "RECENT"]) @@ -166,6 +170,7 @@ Object in the mailbox. the set parameter is a number or an array of numbers or a Range object. the number is a message sequence number (fetch) or a unique identifier (uid_fetch). + The return value is an array of ((<Net::IMAP::FetchData>)). ex). p imap.fetch(6..8, "UID") @@ -188,6 +193,7 @@ Object in the mailbox. the set parameter is a number or an array of numbers or a Range object. the number is a message sequence number (store) or a unique identifier (uid_store). + The return value is an array of ((<Net::IMAP::FetchData>)). ex). p imap.store(6..8, "+FLAGS", [:Deleted]) @@ -210,6 +216,443 @@ Object p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII") #=> [6, 7, 8, 1] +== Net::IMAP::ContinuationRequest + +Net::IMAP::ContinuationRequest represents command continuation requests. + +The command continuation request response is indicated by a "+" token +instead of a tag. This form of response indicates that the server is +ready to accept the continuation of a command from the client. The +remainder of this response is a line of text. + + continue_req ::= "+" SPACE (resp_text / base64) + +=== Super Class + +Struct + +=== Methods + +: data + Returns the data (Net::IMAP::ResponseText). + +: raw_data + Returns the raw data string. + +== Net::IMAP::UntaggedResponse + +Net::IMAP::UntaggedResponse represents untagged responses. + +Data transmitted by the server to the client and status responses +that do not indicate command completion are prefixed with the token +"*", and are called untagged responses. + + response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye / + mailbox_data / message_data / capability_data) + +=== Super Class + +Struct + +=== Methods + +: name + Returns the name such as "FLAGS", "LIST", "FETCH".... + +: data + Returns the data such as an array of flag symbols, + a ((<Net::IMAP::MailboxList>)) object.... + +: raw_data + Returns the raw data string. + +== Net::IMAP::TaggedResponse + +Net::IMAP::TaggedResponse represents tagged responses. + +The server completion result response indicates the success or +failure of the operation. It is tagged with the same tag as the +client command which began the operation. + + response_tagged ::= tag SPACE resp_cond_state CRLF + + tag ::= 1*<any ATOM_CHAR except "+"> + + resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text + +=== Super Class + +Struct + +=== Methods + +: tag + Returns the tag. + +: name + Returns the name. the name is one of "OK", "NO", "BAD". + +: data + Returns the data. See ((<Net::IMAP::ResponseText>)). + +: raw_data + Returns the raw data string. + +== Net::IMAP::ResponseText + +Net::IMAP::ResponseText represents texts of responses. +The text may be prefixed by the response code. + + resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text) + ;; text SHOULD NOT begin with "[" or "=" + +=== Super Class + +Struct + +=== Methods + +: code + Returns the response code. See ((<Net::IMAP::ResponseCode>)). + +: text + Returns the text. + +== Net::IMAP::ResponseCode + +Net::IMAP::ResponseCode represents response codes. + + resp_text_code ::= "ALERT" / "PARSE" / + "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" / + "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / + "UIDVALIDITY" SPACE nz_number / + "UNSEEN" SPACE nz_number / + atom [SPACE 1*<any TEXT_CHAR except "]">] + +=== SuperClass + +Struct + +=== Methods + +: name + Returns the name such as "ALERT", "PERMANENTFLAGS", "UIDVALIDITY".... + +: data + Returns the data if it exists. + +== Net::IMAP::MailboxList + +Net::IMAP::MailboxList represents contents of the LIST response. + + mailbox_list ::= "(" #("\Marked" / "\Noinferiors" / + "\Noselect" / "\Unmarked" / flag_extension) ")" + SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox + +=== Super Class + +Struct + +=== Methods + +: attr + Returns the name attributes. Each name attribute is a symbol + capitalized by String#capitalize, such as :Noselect (not :NoSelect). + +: delim + Returns the hierarchy delimiter + +: name + Returns the mailbox name. + +== Net::IMAP::StatusData + +Net::IMAP::StatusData represents contents of the STATUS response. + +=== Super Class + +Object + +=== Methods + +: mailbox + Returns the mailbox name. + +: attr + Returns a hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT", + "UIDVALIDITY", "UNSEEN". Each value is a number. + +== Net::IMAP::FetchData + +Net::IMAP::FetchData represents contents of the FETCH response. + +=== Super Class + +Object + +=== Methods + +: seqno + Returns the message sequence number. + (Note: not the unique identifier, even for the UID command response.) + +: attr + Returns a hash. Each key is a data item name, and each value is + its value. + + The current data items are: + + : BODY + A form of BODYSTRUCTURE without extension data. + : BODY[<section>]<<origin_octet>> + A string expressing the body contents of the specified section. + : BODYSTRUCTURE + An object that describes the ((<[MIME-IMB]>)) body structure of a message. + See ((<Net::IMAP::BodyTypeBasic>)), ((<Net::IMAP::BodyTypeText>)), + ((<Net::IMAP::BodyTypeMessage>)), ((<Net::IMAP::BodyTypeMultipart>)). + : ENVELOPE + A ((<Net::IMAP::Envelope>)) object that describes the envelope + structure of a message. + : FLAGS + A array of flag symbols that are set for this message. flag symbols + are capitalized by String#capitalize. + : INTERNALDATE + A string representing the internal date of the message. + : RFC822 + Equivalent to BODY[]. + : RFC822.HEADER + Equivalent to BODY.PEEK[HEADER]. + : RFC822.SIZE + A number expressing the ((<[RFC-822]>)) size of the message. + : RFC822.TEXT + Equivalent to BODY[TEXT]. + : UID + A number expressing the unique identifier of the message. + +== Net::IMAP::Envelope + +Net::IMAP::Envelope represents envelope structures of messages. + +=== Super Class + +Struct + +=== Methods + +: date + Retunns a string that represents the date. + +: subject + Retunns a string that represents the subject. + +: from + Retunns an array of ((<Net::IMAP::Address>)) that represents the from. + +: sender + Retunns an array of ((<Net::IMAP::Address>)) that represents the sender. + +: reply_to + Retunns an array of ((<Net::IMAP::Address>)) that represents the reply-to. + +: to + Retunns an array of ((<Net::IMAP::Address>)) that represents the to. + +: cc + Retunns an array of ((<Net::IMAP::Address>)) that represents the cc. + +: bcc + Retunns an array of ((<Net::IMAP::Address>)) that represents the bcc. + +: in_reply_to + Retunns a string that represents the in-reply-to. + +: message_id + Retunns a string that represents the message-id. + +== Net::IMAP::Address + +((<Net::IMAP::Address>)) represents electronic mail addresses. + +=== Super Class + +Struct + +=== Methods + +: name + Returns the phrase from ((<[RFC-822]>)) mailbox. + +: route + Returns the route from ((<[RFC-822]>)) route-addr. + +: mailbox + nil indicates end of ((<[RFC-822]>)) group. + If non-nil and host is nil, returns ((<[RFC-822]>)) group name. + Otherwise, returns ((<[RFC-822]>)) local-part + +: host + nil indicates ((<[RFC-822]>)) group syntax. + Otherwise, returns ((<[RFC-822]>)) domain name. + +== Net::IMAP::ContentDisposition + +Net::IMAP::ContentDisposition represents Content-Disposition fields. + +=== Super Class + +Struct + +=== Methods + +: dsp_type + Returns the disposition type. + +: param + Returns a hash that represents parameters of the Content-Disposition + field. + +== Net::IMAP::BodyTypeBasic + +Net::IMAP::BodyTypeBasic represents basic body structures of messages. + +=== Super Class + +Struct + +=== Methods + +: media_type + Returns the content media type name as defined in ((<[MIME-IMB]>)). + +: subtype + Returns the content subtype name as defined in ((<[MIME-IMB]>)). + +: param + Returns a hash that represents parameters as defined in + ((<[MIME-IMB]>)). + +: content_id + Returns a string giving the content id as defined in ((<[MIME-IMB]>)). + +: description + Returns a string giving the content description as defined in + ((<[MIME-IMB]>)). + +: encoding + Returns a string giving the content transfer encoding as defined in + ((<[MIME-IMB]>)). + +: size + Returns a number giving the size of the body in octets. + +: md5 + Returns a string giving the body MD5 value as defined in ((<[MD5]>)). + +: disposition + Returns a ((<Net::IMAP::ContentDisposition>)) object giving + the content disposition. + +: language + Returns a string or an array of strings giving the body + language value as defined in [LANGUAGE-TAGS]. + +: extension + Returns extension data. + +: multipart? + Returns false. + +== Net::IMAP::BodyTypeText + +Net::IMAP::BodyTypeText represents TEXT body structures of messages. + +=== Super Class + +Struct + +=== Methods + +: lines + Returns the size of the body in text lines. + +And Net::IMAP::BodyTypeText has all methods of ((<Net::IMAP::BodyTypeBasic>)). + +== Net::IMAP::BodyTypeMessage + +Net::IMAP::BodyTypeMessage represents MESSAGE/RFC822 body structures of messages. + +=== Super Class + +Struct + +=== Methods + +: envelope + Returns a ((<Net::IMAP::Envelope>)) giving the envelope structure. + +: body + Returns an object giving the body structure. + +And Net::IMAP::BodyTypeMessage has all methods of ((<Net::IMAP::BodyTypeText>)). + +== Net::IMAP::BodyTypeText + +=== Super Class + +Struct + +=== Methods + +: media_type + Returns the content media type name as defined in ((<[MIME-IMB]>)). + +: subtype + Returns the content subtype name as defined in ((<[MIME-IMB]>)). + +: parts + Returns multiple parts. + +: param + Returns a hash that represents parameters as defined in + ((<[MIME-IMB]>)). + +: disposition + Returns a ((<Net::IMAP::ContentDisposition>)) object giving + the content disposition. + +: language + Returns a string or an array of strings giving the body + language value as defined in [LANGUAGE-TAGS]. + +: extension + Returns extension data. + +: multipart? + Returns true. + +== References + +: [IMAP] + M. Crispin, "INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1", + RFC 2060, December 1996. + +: [LANGUAGE-TAGS] + Alvestrand, H., "Tags for the Identification of + Languages", RFC 1766, March 1995. + +: [MD5] + Myers, J., and M. Rose, "The Content-MD5 Header Field", RFC + 1864, October 1995. + +: [MIME-IMB] + Freed, N., and N. Borenstein, "MIME (Multipurpose Internet + Mail Extensions) Part One: Format of Internet Message Bodies", RFC + 2045, November 1996. + +: [RFC-822] + Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", STD 11, RFC 822, University of Delaware, August 1982. + =end require "socket" @@ -702,7 +1145,7 @@ module Net Address = Struct.new(:name, :route, :mailbox, :host) ContentDisposition = Struct.new(:dsp_type, :param) - class BodyTypeBasic < Struct.new(:media_type, :media_subtype, + class BodyTypeBasic < Struct.new(:media_type, :subtype, :param, :content_id, :description, :encoding, :size, :md5, :disposition, :language, @@ -710,9 +1153,15 @@ module Net def multipart? return false end + + def media_subtype + $stderr.printf("warning: media_subtype is obsolete.\n") + $stderr.printf(" use subtype instead.\n") + return subtype + end end - class BodyTypeText < Struct.new(:media_type, :media_subtype, + class BodyTypeText < Struct.new(:media_type, :subtype, :param, :content_id, :description, :encoding, :size, :lines, @@ -721,9 +1170,15 @@ module Net def multipart? return false end + + def media_subtype + $stderr.printf("warning: media_subtype is obsolete.\n") + $stderr.printf(" use subtype instead.\n") + return subtype + end end - class BodyTypeMessage < Struct.new(:media_type, :media_subtype, + class BodyTypeMessage < Struct.new(:media_type, :subtype, :param, :content_id, :description, :encoding, :size, :envelope, :body, :lines, @@ -732,15 +1187,27 @@ module Net def multipart? return false end + + def media_subtype + $stderr.printf("warning: media_subtype is obsolete.\n") + $stderr.printf(" use subtype instead.\n") + return subtype + end end - class BodyTypeMultipart < Struct.new(:media_type, :media_subtype, + class BodyTypeMultipart < Struct.new(:media_type, :subtype, :parts, :param, :disposition, :language, :extension) def multipart? return true end + + def media_subtype + $stderr.printf("warning: media_subtype is obsolete.\n") + $stderr.printf(" use subtype instead.\n") + return subtype + end end class ResponseParser @@ -1475,6 +1942,12 @@ module Net when /\A(?:UIDVALIDITY|UIDNEXT|UNSEEN)\z/n match(T_SPACE) result = ResponseCode.new(name, number) + else + match(T_SPACE) + @lex_state = EXPR_CTEXT + token = match(T_TEXT) + @lex_state = EXPR_BEG + result = ResponseCode.new(name, token.value) end match(T_RBRA) @lex_state = EXPR_RTEXT @@ -1579,7 +2052,7 @@ module Net if @str.index(/\(([^)]*)\)/ni, @pos) @pos = $~.end(0) return $1.scan(FLAG_REGEXP).collect { |flag, atom| - atom || flag.intern + atom || flag.capitalize.intern } else parse_error("invalid flag list") diff --git a/lib/net/pop.rb b/lib/net/pop.rb index 4f6eb930a4..8f3f978e8c 100644 --- a/lib/net/pop.rb +++ b/lib/net/pop.rb @@ -1,6 +1,6 @@ =begin -= net/pop.rb version 1.1.32 += net/pop.rb version 1.1.34 written by Minero Aoki <aamine@dp.u-netsurf.ne.jp> @@ -184,6 +184,7 @@ module Net 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' @@ -206,9 +207,10 @@ module Net end - def initialize( addr = nil, port = nil ) - super + def initialize( addr = nil, port = nil, apop = false ) + super addr, port @mails = nil + @apop = false end attr :mails @@ -238,6 +240,11 @@ module Net private + def conn_command( sock ) + @command = + (@apop ? type.apop_command_type : type.command_type).new(sock) + end + def do_start( acnt, pwd ) @command.auth( acnt, pwd ) diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb index 161024cfe2..343721add3 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -1,6 +1,6 @@ =begin -= net/protocol.rb version 1.1.32 += net/protocol.rb version 1.1.34 written by Minero Aoki <aamine@dp.u-netsurf.ne.jp> @@ -59,13 +59,22 @@ Object =end require 'socket' +require 'timeout' module Net + module NetPrivate + end + + def self.net_private( &block ) + ::Net::NetPrivate.module_eval( &block ) + end + + class Protocol - Version = '1.1.32' + Version = '1.1.34' class << self @@ -116,8 +125,12 @@ module Net @command = nil @socket = nil - @active = false - @pipe = nil + @active = false + + @open_timeout = nil + @read_timeout = nil + + @dout = nil end attr_reader :address @@ -126,10 +139,26 @@ module Net attr_reader :command attr_reader :socket + attr_accessor :open_timeout + attr_accessor :read_timeout + + def active? + @active + end + + def set_debug_output( arg ) # un-documented + @dout = arg + end + + alias set_pipe set_debug_output + def inspect "#<#{type} #{address}:#{port} open=#{active?}>" end + # + # open session + # def start( *args ) return false if active? @@ -146,45 +175,59 @@ module Net end end + private + def _start( args ) connect do_start( *args ) @active = true end - private :_start - - def finish - return false unless active? - do_finish unless @command.critical? - disconnect - @active = false - true + def connect + conn_socket @address, @port + conn_command @socket + on_connect end - def active? - @active + def re_connect + @socket.reopen @open_timeout + on_connect end - def set_pipe( arg ) # un-documented - @pipe = arg + 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 ) + end - private - + def on_connect + end def do_start end - def do_finish - @command.quit + # + # close session + # + + public + + def finish + return false unless active? + + do_finish if @command and not @command.critical? + disconnect + @active = false + true end + private - def connect( addr = @address, port = @port ) - @socket = type.socket_type.open( addr, port, @pipe ) - @command = type.command_type.new( @socket ) + def do_finish + @command.quit end def disconnect @@ -192,7 +235,11 @@ module Net if @socket and not @socket.closed? then @socket.close end - @socket = nil + @socket = nil + on_disconnect + end + + def on_disconnect end end @@ -200,6 +247,7 @@ module Net Session = Protocol + net_private { class Response @@ -223,6 +271,8 @@ module Net end + } + class ProtocolError < StandardError; end class ProtoSyntaxError < ProtocolError; end @@ -294,8 +344,7 @@ module Net - module NetPrivate - + net_private { class WriteAdapter @@ -311,7 +360,11 @@ module Net def write( str ) @sock.__send__ @mid, str end - alias << write + + def <<( str ) + @sock.__send__ @mid, str + self + end end @@ -407,6 +460,7 @@ module Net @critical = false end + private def critical @@ -431,22 +485,30 @@ module Net class Socket - def initialize( addr, port, pipe = nil ) + def initialize( addr, port, otime = nil, rtime = nil, dout = nil ) @addr = addr @port = port - @pipe = pipe - @prepipe = nil - @closed = true - @ipaddr = '' + @read_timeout = rtime + + @debugout = dout + + @socket = nil @sending = '' @buffer = '' - @socket = TCPsocket.new( addr, port ) - @closed = false - @ipaddr = @socket.addr[3] + 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 @@ -454,27 +516,31 @@ module Net end def inspect - "#<#{type} open=#{!@closed}>" + "#<#{type} #{closed? ? 'closed' : 'opened'}>" end - def reopen - unless closed? then - close - @buffer = '' - end - @socket = TCPsocket.new( @addr, @port ) - @closed = false + def reopen( otime = nil ) + D 'reopening...' + close + connect otime + D 'reopened' end attr :socket, true def close - @socket.close - @closed = true + if @socket then + @socket.close + D 'closed' + else + D 'close call for already closed socket' + end + @socket = nil + @buffer = '' end def closed? - @closed + not @socket end def address @@ -486,7 +552,8 @@ module Net attr_reader :port def ip_address - @ipaddr.dup + @socket or return '' + @socket.addr[3] end alias ipaddr ip_address @@ -494,57 +561,64 @@ module Net attr_reader :sending - ### - ### read - ### + # + # read + # + + public CRLF = "\r\n" - def read( len, dest = '' ) - @pipe << "reading #{len} bytes...\n" if @pipe; pipeoff + def read( len, dest = '', ignerr = false ) + D_off "reading #{len} bytes..." rsize = 0 - while rsize + @buffer.size < len do - rsize += writeinto( dest, @buffer.size ) - fill_rbuf + begin + while rsize + @buffer.size < len do + rsize += rbuf_moveto( dest, @buffer.size ) + rbuf_fill + end + rbuf_moveto dest, len - rsize + rescue EOFError + raise unless igneof end - writeinto( dest, len - rsize ) - @pipe << "read #{len} bytes\n" if pipeon + D_on "read #{len} bytes" dest end - def read_all( dest = '' ) - @pipe << "reading all...\n" if @pipe; pipeoff + D_off 'reading all...' rsize = 0 begin while true do - rsize += writeinto( dest, @buffer.size ) - fill_rbuf + rsize += rbuf_moveto( dest, @buffer.size ) + rbuf_fill end rescue EOFError ; end - @pipe << "read #{rsize} bytes\n" if pipeon + D_on "read #{rsize} bytes" dest end - - def readuntil( target ) - while true do - idx = @buffer.index( target ) - break if idx - fill_rbuf - end - + def readuntil( target, igneof = false ) dest = '' - writeinto( dest, idx + target.size ) + begin + while true do + idx = @buffer.index( target ) + break if idx + rbuf_fill + end + rbuf_moveto dest, idx + target.size + rescue EOFError + raise unless igneof + rbuf_moveto dest, @buffer.size + end dest end - def readline ret = readuntil( "\n" ) @@ -552,9 +626,8 @@ module Net ret end - def read_pendstr( dest ) - @pipe << "reading text...\n" if @pipe; pipeoff + D_off 'reading text...' rsize = 0 while (str = readuntil("\r\n")) != ".\r\n" do @@ -563,14 +636,13 @@ module Net dest << str end - @pipe << "read #{rsize} bytes\n" if pipeon + D_on "read #{rsize} bytes" dest end - # private use only (can not handle 'break') def read_pendlist - @pipe << "reading list...\n" if @pipe; pipeoff + D_off 'reading list...' str = nil i = 0 @@ -580,55 +652,59 @@ module Net yield str end - @pipe << "read #{i} items\n" if pipeon + D_on "read #{i} items" end private - READ_BLOCK = 1024 * 8 + READ_SIZE = 1024 * 4 - def fill_rbuf - @buffer << @socket.sysread( READ_BLOCK ) + def rbuf_fill + unless IO.select [@socket], nil, nil, @read_timeout then + on_read_timeout + end + @buffer << @socket.sysread( READ_SIZE ) end - def writeinto( dest, len ) + def on_read_timeout + raise TimeoutError, "socket read timeout (#{@read_timeout} sec)" + end + + def rbuf_moveto( dest, len ) bsi = @buffer.size - dest << @buffer[ 0, len ] + s = @buffer[ 0, len ] + dest << s @buffer = @buffer[ len, bsi - len ] - @pipe << %{read "#{Net.quote dest}"\n} if @pipe + @debugout << %<read "#{Net.quote s}"\n> if @debugout len end - ### - ### write - ### + # + # write interfece + # public - def write( str ) writing { do_write str } end - def writeline( str ) writing { - do_write str - do_write "\r\n" + do_write str + "\r\n" } end - def write_bin( src, block ) writing { if block then - block.call WriteAdapter.new( self, :do_write ) + block.call ::Net::NetPrivate::WriteAdapter.new( self, :do_write ) else src.each do |bin| do_write bin @@ -637,19 +713,18 @@ module Net } end - def write_pendstr( src, block ) - @pipe << "writing text from #{src.type}\n" if @pipe; pipeoff + D_off "writing text from #{src.type}" wsize = use_each_crlf_line { if block then - block.call WriteAdapter.new( self, :wpend_in ) + block.call ::Net::NetPrivate::WriteAdapter.new( self, :wpend_in ) else wpend_in src end } - @pipe << "wrote #{wsize} bytes text\n" if pipeon + D_on "wrote #{wsize} bytes text" wsize end @@ -696,17 +771,17 @@ module Net beg = 0 buf = @wbuf while buf.index( /\n|\r\n|\r/, beg ) do - m = $~ + m = Regexp.last_match if m.begin(0) == buf.size - 1 and buf[-1] == ?\r then # "...\r" : can follow "\n..." break end - str = buf[ beg, m.begin(0) - beg ] + str = buf[ beg ... m.begin(0) ] str.concat "\r\n" yield str beg = m.end(0) end - @wbuf = buf[ beg, buf.size - beg ] + @wbuf = buf[ beg ... buf.size ] end end @@ -736,6 +811,7 @@ module Net yield end end + yield unless @wbuf.empty? end end @@ -746,17 +822,17 @@ module Net yield - if @pipe then - @pipe << 'write "' - @pipe << @sending - @pipe << "\"\n" + if @debugout then + @debugout << 'write "' + @debugout << @sending + @debugout << "\"\n" end @socket.flush @writtensize end def do_write( arg ) - if @pipe or @sending.size < 128 then + if @debugout or @sending.size < 128 then @sending << Net.quote( arg ) else @sending << '...' unless @sending[-1] == ?. @@ -768,22 +844,25 @@ module Net end - def pipeoff - @prepipe = @pipe - @pipe = nil - @prepipe + def D_off( msg ) + D msg + @savedo, @debugout = @debugout, nil end - def pipeon - @pipe = @prepipe - @prepipe = nil - @pipe + def D_on( msg ) + @debugout = @savedo + D msg end - end + def D( msg ) + @debugout or return + @debugout << msg + @debugout << "\n" + end + end - end # module Net::NetPrivate + } def Net.quote( str ) diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb index 9679984e2c..befc1adf03 100644 --- a/lib/net/smtp.rb +++ b/lib/net/smtp.rb @@ -1,6 +1,6 @@ =begin -= net/smtp.rb version 1.1.32 += net/smtp.rb version 1.1.34 written by Minero Aoki <aamine@dp.u-netsurf.ne.jp> @@ -30,10 +30,8 @@ Net::Protocol === Methods -: start( helo_domain = Socket.gethostname, \ - account = nil, password = nil, authtype = nil ) -: start( helo_domain = Socket.gethostname, \ - account = nil, password = nil, authtype = nil ) {|smtp| .... } +: start( helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) +: start( helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) {|smtp| .... } opens TCP connection and starts SMTP session. If protocol had been started, do nothing and return false. @@ -53,10 +51,10 @@ Net::Protocol to_addrs must be a String(s) or an Array of String. Exceptions which SMTP raises are: - * Net::ProtoSyntaxError: syntax error (errno.500) - * Net::ProtoFatalError: fatal error (errno.550) - * Net::ProtoUnknownError: unknown error - * Net::ProtoServerBusy: temporary error (errno.420/450) + * Net::ProtoSyntaxError: syntax error (errno.500) + * Net::ProtoFatalError: fatal error (errno.550) + * Net::ProtoUnknownError: unknown error + * Net::ProtoServerBusy: temporary error (errno.420/450) # usage example @@ -153,12 +151,15 @@ module Net end end - if user and secret then + if user or secret then + (user and secret) or + raise ArgumentError, 'both of account and password are required' + mid = 'auth_' + (authtype || 'cram_md5').to_s - unless @command.respond_to? mid then - raise ArgumentError, "wrong auth type #{authtype.to_s}" - end - @command.send mid, user, secret + @command.respond_to? mid or + raise ArgumentError, "wrong auth type #{authtype.to_s}" + + @command.__send__ mid, user, secret end end diff --git a/lib/net/telnet.rb b/lib/net/telnet.rb index 87790c0300..380e834bea 100644 --- a/lib/net/telnet.rb +++ b/lib/net/telnet.rb @@ -4,7 +4,7 @@ net/telnet.rb - simple telnet client library -Version 1.6.2 +Version 1.6.3 Wakou Aoyama <wakou@fsinet.or.jp> @@ -239,10 +239,11 @@ module Net CR = "\015" LF = "\012" EOL = CR + LF - VERSION = "1.6.2" - RELEASE_DATE = "2000-12-25" - VERSION_CODE = 162 - RELEASE_CODE = 20001225 + VERSION = '1.6.3' + RELEASE_DATE = '2001-02-26' + VERSION_CODE = 163 + RELEASE_CODE = 20010226 + REVISION = '$Id$' def initialize(options) @options = options @@ -346,14 +347,13 @@ module Net attr :sock def telnetmode(mode = nil) - if mode - if (true == mode or false == mode) - @options["Telnetmode"] = mode - else - raise ArgumentError, "required true or false" - end - else + case mode + when nil @options["Telnetmode"] + when true, false + @options["Telnetmode"] = mode + else + raise ArgumentError, "required true or false" end end @@ -366,14 +366,13 @@ module Net end def binmode(mode = nil) - if mode - if (true == mode or false == mode) - @options["Binmode"] = mode - else - raise ArgumentError, "required true or false" - end - else + case mode + when nil @options["Binmode"] + when true, false + @options["Binmode"] = mode + else + raise ArgumentError, "required true or false" end end @@ -599,181 +598,7 @@ end == HISTORY -* Mon Dec 25 01:37:43 JST 2000 - wakou - * version 1.6.2 - * Regexp::last_match[1] --> $1 - -* Mon Dec 11 00:16:51 JST 2000 - wakou - * version 1.6.1 - * $1 --> Regexp::last_match[1] - -* 2000/09/12 05:37:35 - matz - * change: iterator? --> block_given? - -* Tue Sep 12 06:52:48 JST 2000 - wakou - * version 1.6.0 - * correct: document. - thanks to Kazuhiro NISHIYAMA <zn@mbf.nifty.com> - * add: Telnet#puts(). - -* Sun Jun 18 23:31:44 JST 2000 - wakou - * version 1.5.0 - * change: version syntax. old: x.yz, now: x.y.z - -* 2000/05/24 06:57:38 - wakou - * version 1.40 - * improve: binmode(), telnetmode() interface. - thanks to Dave Thomas <Dave@thomases.com> - -* 2000/05/09 22:02:56 - wakou - * version 1.32 - * require English.rb - -* 2000/05/02 21:48:39 - wakou - * version 1.31 - * Proxy option: can receive IO object. - -* 2000/04/03 18:27:02 - wakou - * version 1.30 - * telnet.rb --> net/telnet.rb - -* 2000/01/24 17:02:57 - wakou - * version 1.20 - * respond to "IAC WILL x" with "IAC DONT x" - * respond to "IAC WONT x" with "IAC DONT x" - * better dumplog format. - thanks to WATANABE Hirofumi <Hirofumi.Watanabe@jp.sony.com> - -* 2000/01/18 17:47:31 - wakou - * version 1.10 - * bug fix: write method - * respond to "IAC WILL BINARY" with "IAC DO BINARY" - -* 1999/10/04 22:51:26 - wakou - * version 1.00 - * bug fix: waitfor(preprocess) method. - thanks to Shin-ichiro Hara <sinara@blade.nagaokaut.ac.jp> - * add simple support for AO, DM, IP, NOP, SB, SE - * COUTION! TimeOut --> TimeoutError - -* 1999/09/21 21:24:07 - wakou - * version 0.50 - * add write method - -* 1999/09/17 17:41:41 - wakou - * version 0.40 - * bug fix: preprocess method - -* 1999/09/14 23:09:05 - wakou - * version 0.30 - * change prompt check order. - not IO::select([@sock], nil, nil, waittime) and prompt === line - --> prompt === line and not IO::select([@sock], nil, nil, waittime) - -* 1999/09/13 22:28:33 - wakou - * version 0.24 - * Telnet#login: if ommit password, then not require password prompt. - -* 1999/08/10 05:20:21 - wakou - * version 0.232 - * STATUS OUTPUT sample code typo. - thanks to Tadayoshi Funaba <tadf@kt.rim.or.jp> - host = Telnet.new({"Hosh" => "localhost"){|c| print c } - --> host = Telnet.new({"Host" => "localhost"){|c| print c } - -* 1999/07/16 13:39:42 - wakou - * version 0.231 - * TRUE --> true, FALSE --> false - -* 1999/07/15 22:32:09 - wakou - * version 0.23 - * waitfor: if end of file reached, then return nil. - -* 1999/06/29 09:08:51 - wakou - * version 0.22 - * new, waitfor, cmd: {"Timeout" => false} # ignore timeout - -* 1999/06/28 18:18:55 - wakou - * version 0.21 - * waitfor: not rescue (EOFError) - -* 1999/06/04 06:24:58 - wakou - * version 0.20 - * waitfor: support for divided telnet command - -* 1999/05/22 - wakou - * version 0.181 - * bug fix: print method - -* 1999/05/14 - wakou - * version 0.18 - * respond to "IAC WON'T SGA" with "IAC DON'T SGA" - * DON'T SGA : end of line --> CR + LF - * bug fix: preprocess method - -* 1999/04/30 - wakou - * version 0.17 - * bug fix: $! + "\n" --> $!.to_s + "\n" - -* 1999/04/11 - wakou - * version 0.163 - * STDOUT.write(message) --> yield(message) if iterator? - -* 1999/03/17 - wakou - * version 0.162 - * add "Proxy" option - * required timeout.rb - -* 1999/02/03 - wakou - * version 0.161 - * select --> IO::select - -* 1998/10/09 - wakou - * version 0.16 - * preprocess method change for the better - * add binmode method. - * change default Binmode. TRUE --> FALSE - -* 1998/10/04 - wakou - * version 0.15 - * add telnetmode method. - -* 1998/09/22 - wakou - * version 0.141 - * change default prompt. /[$%#>] $/ --> /[$%#>] \Z/ - -* 1998/09/01 - wakou - * version 0.14 - * IAC WILL SGA send EOL --> CR+NULL - * IAC WILL SGA IAC DO BIN send EOL --> CR - * NONE send EOL --> LF - * add Dump_log option. - -* 1998/08/25 - wakou - * version 0.13 - * add print method. - -* 1998/08/05 - wakou - * version 0.122 - * support for HP-UX 10.20. - thanks to WATANABE Tetsuya <tetsu@jpn.hp.com> - * socket.<< --> socket.write - -* 1998/07/15 - wakou - * version 0.121 - * string.+= --> string.concat - -* 1998/06/01 - wakou - * version 0.12 - * add timeout, waittime. - -* 1998/04/21 - wakou - * version 0.11 - * add realtime output. - -* 1998/04/13 - wakou - * version 0.10 - * first release. - -$Date$ +delete. see cvs log. + + =end |