diff --git a/lib/net/http.rb b/lib/net/http.rb index b908b8ae79..bdbcb82fd5 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -38,11 +38,14 @@ class HTTPBadResponse < HTTPError; end == Methods -: get( path, header = nil, ret = '' ) +: get( path, header = nil, dest = '' ) +: get( path, header = nil ) {|str| .... } get data from "path" on connecting host. - "header" is a Hash like { 'Accept' => '*/*', ... }. - The data will be written to "ret" using "<<" method. - This method returns response header (Hash) and "ret". + "header" must be a Hash like { 'Accept' => '*/*', ... }. + Data is written to "dest" by using "<<" method. + This method returns response header (Hash) and "dest". + + If called as iterator, give a part String of entity body. : head( path, header = nil ) get only header from "path" on connecting host. @@ -53,6 +56,30 @@ class HTTPBadResponse < HTTPError; end 'content-type' => 'Content-Type: text/html', ... } +: post( path, data, header = nil, dest = '' ) +: post( path, data, header = nil ) {|str| .... } + post "data"(must be String now) to "path" (and get entity body). + "header" must be a Hash like { 'Accept' => '*/*', ... }. + Data is written to "dest" by using "<<" method. + This method returns response header (Hash) and "dest". + + If called as iterator, give a part String of entity body. + + ATTENTION: entity body could be empty + +: get2( path, header = nil ) + send GET request for "path". + "header" must be a Hash like { 'Accept' => '*/*', ... }. + This method returns response header (Hash). + +: get_body( dest = '' ) +: get_body {|str| .... } + gets entity body of forwarded 'get2' or 'post2' methods. + Data is written in "dest" by using "<<" method. + This method returns "dest". + + If called as iterator, give a part String of entity body. + =end class HTTP < Protocol @@ -61,19 +88,71 @@ class HTTPBadResponse < HTTPError; end protocol_param :command_type, '::Net::HTTPCommand' - def get( path, u_header = nil, ret = '' ) + def get( path, u_header = nil, dest = nil, &block ) u_header ||= {} - header = connecting( u_header ) { - @command.get ret, edit_path(path), u_header + if block then + dest = ReadAdapter.new( block ) + ret = nil + else + dest = ret = '' + end + resp = nil + connecting( u_header ) { + @command.get edit_path(path), u_header + resp = @command.get_response + @command.try_get_body( resp, dest ) } - return header, ret + return resp['http-header'], ret end - def head( path, u_header = nil ) + def get2( path, u_header = {} ) + only_header( :get, path, u_header ) + end + + def get_body( dest = '', &block ) + if block then + dest = ReadAdapter.new( block ) + end + @command.try_get_body @response, dest + ensure_termination @u_header + + dest + end + + def head( path, u_header = {} ) + ret = only_header( :head, path, u_header )['http-header'] + ensure_termination u_header + ret + end + + def post( path, data, u_header = nil, dest = nil, &block ) u_header ||= {} - header = connecting( u_header ) { - @command.head edit_path(path), u_header + if block then + dest = ReadAdapter.new( block ) + ret = nil + else + dest = ret = '' + end + resp = nil + connecting( u_header, true ) { + @command.post path, u_header, data + resp = @command.get_response + @command.try_get_body( resp, dest ) + } + + return resp['http-header'], ret + end + + def post2( path, data, u_header = {} ) + only_header :post, path, u_header, data + end + + # not tested because I could not setup apache (__;;; + def put( path, src = nil, u_header = {}, &block ) + u_header ||= u_header + connecting( u_header, true ) { + @command.put path, u_header, src, dest } header @@ -83,30 +162,51 @@ class HTTPBadResponse < HTTPError; end private + def only_header( mid, path, u_header, data = nil ) + @u_header = u_header ? procheader(u_header) : {} + @response = nil + ensure_connection @u_header + if data then + @command.send mid, edit_path(path), @u_header, data + else + @command.send mid, edit_path(path), @u_header + end + @response = @command.get_response + @response['http-header'] + end + + # called when connecting def do_finish unless @socket.closed? then - @command.head '/', { 'Connection' => 'Close' } + begin + @command.head '/', { 'Connection' => 'Close' } + rescue EOFError + end end end - def connecting( u_header ) + def connecting( u_header, putp = false ) u_header = procheader( u_header ) + ensure_connection u_header + yield + ensure_termination u_header + end + def ensure_connection( u_header ) if not @socket then u_header['Connection'] = 'Close' start elsif @socket.closed? then @socket.reopen end + end - header = yield - + def ensure_termination( u_header ) unless keep_alive? u_header then @socket.close end - - header + @u_header = @response = nil end def keep_alive?( header ) @@ -155,6 +255,20 @@ class HTTPBadResponse < HTTPError; end HTTPSession = HTTP + class HTTPSuccessCode < SuccessCode; end + class HTTPCreatedCode < SuccessCode; end + class HTTPAcceptedCode < SuccessCode; end + class HTTPNoContentCode < SuccessCode; end + class HTTPResetContentCode < SuccessCode; end + class HTTPPartialContentCode < SuccessCode; end + + class HTTPMultipleChoiceCode < RetryCode; end + class HTTPMovedPermanentlyCode < RetryCode; end + class HTTPMovedTemporarilyCode < RetryCode; end + class HTTPNotModifiedCode < RetryCode; end + class HTTPUseProxyCode < RetryCode; end + + class HTTPCommand < Command HTTPVersion = '1.1' @@ -173,40 +287,25 @@ class HTTPBadResponse < HTTPError; end attr_reader :http_version - def get( ret, path, u_header = nil ) - header = get_response( - sprintf( 'GET %s HTTP/%s', path, HTTPVersion ), u_header ) - if chunked? header then - clen = read_chunked_body( ret ) - header.delete 'transfer-encoding' - header[ 'content-length' ] = "Content-Length: #{clen}" - else - if clen = content_length( header ) then - @socket.read clen, ret - else - @socket.read_all ret - end - end - - header + def get( path, u_header ) + request sprintf('GET %s HTTP/%s', path, HTTPVersion), u_header + end + + def head( path, u_header ) + request sprintf('HEAD %s HTTP/%s', path, HTTPVersion), u_header end - - def head( path, u_header = nil ) - get_response sprintf( 'HEAD %s HTTP/%s', path, HTTPVersion ), u_header + def post( path, u_header, data ) + request sprintf('POST %s HTTP/%s', path, HTTPVersion), u_header + @socket.write data end - - # not work - def post( path, u_header = nil ) - get_response sprintf( 'POST %s HTTP/%s', path, HTTPVersion ), u_header + def put( path, u_header, src ) + request sprintf('PUT %s HTTP/%s', path, HTTPVersion), u_header + @socket.write_bin src end - # not work - def put( path, u_header = nil ) - get_response sprintf( 'PUT %s HTTP/%s', path, HTTPVersion ), u_header - end # def delete @@ -215,19 +314,64 @@ class HTTPBadResponse < HTTPError; end # def options + def get_response + rep = get_reply + rep = get_reply while ContinueCode === rep + header = {} + while true do + line = @socket.readline + break if line.empty? + nm = /\A[^:]+/.match( line )[0].strip.downcase + header[nm] = line + end + + rep['http-header'] = header + reply_must rep, SuccessCode + + rep + end + + def get_body( rep, dest ) + header = rep['http-header'] + if chunked? header then + read_chunked( dest, header ) + else + if clen = content_length( header ) then + @socket.read clen, dest + else + ### + ### "multipart/bytelenges" check should be done here ... + ### + @socket.read_all dest + end + end + end + + def try_get_body( rep, dest ) + rep = get_reply while ContinueCode === rep + return nil unless rep['body-exist'] + + get_body rep, dest + end + + private - def get_response( line, u_header ) - @socket.writeline line - write_header u_header - rep = get_reply - header = read_header - reply_must rep, SuccessCode - - header + def request( req, u_header ) + @socket.writeline req + if u_header then + header = @in_header.dup.update( u_header ) + else + header = @in_header + end + header.each do |n,v| + @socket.writeline n + ': ' + v + end + @socket.writeline '' end + def get_reply str = @socket.readline unless /\AHTTP\/(\d+\.\d+)?\s+(\d\d\d)\s*(.*)\z/i === str then @@ -237,20 +381,66 @@ class HTTPBadResponse < HTTPError; end status = $2 discrip = $3 + be = false klass = case status[0] when ?1 then case status[2] when ?0 then ContinueCode - when ?1 then SuccessCode + when ?1 then HTTPSuccessCode + else UnknownCode + end + when ?2 then + case status[2] + when ?0 then be = true; HTTPSuccessCode + when ?1 then be = false; HTTPSuccessCode + when ?2 then be = true; HTTPSuccessCode + when ?3 then be = true; HTTPSuccessCode + when ?4 then be = false; HTTPNoContentCode + when ?5 then be = false; HTTPResetContentCode + when ?6 then be = true; HTTPPartialContentCode + else UnknownCode + end + when ?3 then + case status[2] + when ?0 then be = true; HTTPMultipleChoiceCode + when ?1 then be = true; HTTPMovedPermanentryCode + when ?2 then be = true; HTTPMovedTemporarilyCode + when ?3 then be = true; HTTPMovedPermanentryCode + when ?4 then be = false; HTTPNotModifiedCode + when ?5 then be = false; HTTPUseProxyCode else UnknownCode end - when ?2 then SuccessCode - when ?3 then RetryCode when ?4 then ServerBusyCode when ?5 then FatalErrorCode else UnknownCode end - klass.new( status, discrip ) + code = klass.new( status, discrip ) + code['body-exist'] = be + code + end + + def read_chunked( ret, header ) + line = nil + len = nil + total = 0 + + while true do + line = @socket.readline + unless /[0-9a-hA-H]+/ === line then + raise HTTPBadResponse, "chunk size not given" + end + len = $&.hex + break if len == 0 + @socket.read( len, ret ); total += len + @socket.read 2 # \r\n + end + while true do + line = @socket.readline + break if line.empty? + end + + header.delete 'transfer-encoding' + header[ 'content-length' ] = "Content-Length: #{total}" end @@ -274,63 +464,6 @@ class HTTPBadResponse < HTTPError; end false end - - def read_header - header = {} - while true do - line = @socket.readline - break if line.empty? - /\A[^:]+/ === line - nm = $& - nm.strip! - nm.downcase! - header[ nm ] = line - end - - header - end - - def write_header( user ) - if user then - header = @in_header.dup.update user - else - header = @in_header - end - header.each do |n,v| - @socket.writeline n + ': ' + v - end - @socket.writeline '' - - if tmp = header['Connection'] then - /close/i === tmp - else - false - end - end - - def read_chunked_body( ret ) - line = nil - len = nil - total = 0 - - while true do - line = @socket.readline - unless /[0-9a-hA-H]+/ === line then - raise HTTPBadResponse, "chunk size not given" - end - len = $&.hex - break if len == 0 - @socket.read( len, ret ); total += len - @socket.read 2 # \r\n - end - while true do - line = @socket.readline - break if line.empty? - end - - total - end - end diff --git a/lib/net/pop.rb b/lib/net/pop.rb index 440f894f57..616f537f52 100644 --- a/lib/net/pop.rb +++ b/lib/net/pop.rb @@ -216,66 +216,6 @@ Net::POP3 APOPSession = APOP -=begin - -== Net::POP3Command - -POP3 command class. - -=== Super Class - -Net::Command - -=== Class Methods - -: new( socket ) - This method creates new POP3Command object. 'socket' must be ProtocolSocket. - - -=== Methods - -: auth( account, password ) - This method do POP authorization (no RPOP) - In case of failed authorization, raises Protocol::ProtocolError exception. - -: list - a list of mails which existing on server. - The list is an array like "array[ number ] = size". - - ex: - - The list from server is - - 1 2452 - 2 3355 - 4 9842 - : - - then, an array is - - [ nil, 2452, 3355, nil, 9842, ... ] - -: quit - This method ends POP using 'QUIT' commmand. - -: rset - This method reset all changes done in current session, - by sending 'RSET' command. - -: top( num, lines = 0 ) - This method gets all mail header and 'lines' lines body - by sending 'TOP' command. 'num' is mail number. - - WARNING: the TOP command is 'Optional' in RFC1939 (POP3) - -: retr( num : Integer ) - This method gets a mail by 'RETR' command. 'num' is mail number. - -: dele( num : Integer ) - This method deletes a mail on server by 'DELE'. - -=end - class POP3Command < Command @@ -371,22 +311,6 @@ Net::Command end -=begin - -== APOPCommand - -=== Super Class - -POP3Command - -=== Methods - -: auth( account, password ) - This method do authorization by sending 'APOP' command. - If server is not APOP server, this raises Net::ProtoAuthError exception. - On other errors, raises Net::ProtocolError. - -=end class APOPCommand < POP3Command diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb index 1f1df17032..3a200d5d29 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -15,7 +15,7 @@ require 'socket' module Net - Version = '1.1.7' + Version = '1.1.9' =begin @@ -219,27 +219,6 @@ Object Session = Protocol -=begin - -== Net::Command - -=== Super Class - -Object - -=== Class Methods - -: new( socket ) - This method create new Command object. 'socket' must be ProtocolSocket. - This method is abstract class. - - -=== Methods - -: quit - This method dispatch command which ends the protocol. - -=end class Command @@ -317,10 +296,26 @@ Object def initialize( cod, mes ) @code = cod @msg = mes + @data = nil end attr_reader :code, :msg + def []( key ) + if @data then + @data[key] + else + nil + end + end + + def []=( key, val ) + unless h = @data then + @data = h = {} + end + h[key] = val + end + def error!( sending ) mes = <