diff --git a/lib/net/http.rb b/lib/net/http.rb index a3d271c574..6c1161ee9c 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -5,6 +5,9 @@ maintained by Minero Aoki This file is derived from http-access.rb +This library is distributed under the terms of the Ruby license. +You can freely distribute/modify this library. + =end require 'net/session' @@ -17,253 +20,296 @@ class HTTPError < ProtocolError; end class HTTPBadResponse < HTTPError; end -class HTTPSession < Session +=begin - Version = '1.1.2' += HTTP class - session_setvar :port, '80' - session_setvar :command_type, 'Net::HTTPCommand' +== Class Methods - - def get( path = '/', header = nil, ret = '' ) - confirm_connection - @proto.get edit_path(path), header, ret - end - - def head( path = '/', header = nil ) - confirm_connection - @proto.head edit_path(path), header - end - - - private - - - def confirm_connection - if @socket.closed? then - @socket.reopen - end - end +: new( address, port = 80 ) - def do_finish - unless @proto.error_occured or @socket.closed? then - head '/', { 'Connection' => 'Close' } + create new HTTP object. + +: port + + returns HTTP default port, 80 + +: command_type + + returns Command class, HTTPCommand + + +== Methods + +: get( path, header = nil, ret = '' ) + + 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". + +: head( path, header = nil ) + + get only header from "path" on connecting host. + "header" is a Hash like { 'Accept' => '*/*', ... }. + This method returns header as a Hash like + + { 'content-length' => 'Content-Length: 2554', + 'content-type' => 'Content-Type: text/html', + ... } + +=end + + class HTTP < Protocol + + Version = '1.1.3' + + protocol_param :port, '80' + protocol_param :command_type, '::Net::HTTPCommand' + + + def get( path, u_header = nil, ret = '' ) + header = connecting { + @command.get ret, edit_path(path), u_header + } + return header, ret + end + + def head( path, u_header = nil ) + connecting { + @command.head edit_path(path), u_header + } end - end - def edit_path( path ) - path - end + private - class << self - def Proxy( addr, port ) - klass = super - klass.module_eval %- - def edit_path( path ) - 'http://' + address + (port == self.port ? '' : ":\#{port}") + path + + def connecting + if @socket.closed? then + @socket.reopen + end + header = yield + @socket.close unless keep_alive? header + + header + end + + def keep_alive?( header ) + if str = header[ 'connection' ] then + if /\Aconnection:\s*keep-alive/i === str then + return true + end + else + if @http_version == '1.1' then + return true end - - - klass - end - end - -end - -HTTP = HTTPSession - - -class HTTPCommand < Command - - HTTPVersion = '1.1' - - def initialize( sock ) - @http_version = HTTPVersion - - @in_header = {} - @in_header[ 'Host' ] = sock.addr - #@in_header[ 'User-Agent' ] = "Ruby http version #{HTTPSession::Version}" - @in_header[ 'Connection' ] = 'keep-alive' - @in_header[ 'Accept' ] = '*/*' - - super sock - end - - - attr :http_version - - def get( path, u_header = nil, ret = '' ) - 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 - @socket.read content_length( header ), ret - end - @socket.close unless keep_alive? header - - return header, ret - end - - - def head( path, u_header = nil ) - header = get_response( - sprintf( 'HEAD %s HTTP/%s', path, HTTPVersion ), u_header ) - @socket.close unless keep_alive? header - - header - end - - - # def put - - # def delete - - # def trace - - # def options - - - private - - - def do_quit - unless @socket.closed? then - @socket.close - end - end - - def get_response( line, u_header ) - @socket.writeline line - write_header u_header - rep = get_reply - header = read_header - reply_must rep, SuccessCode - - header - end - - def get_reply - str = @socket.readline - unless /\AHTTP\/(\d+\.\d+)?\s+(\d\d\d)\s*(.*)\z/i === str then - raise HTTPBadResponse, "wrong status line format: #{str}" - end - @http_version = $1 - status = $2 - discrip = $3 - - klass = case status[0] - when ?1 then - case status[2] - when ?0 then ContinueCode - when ?1 then SuccessCode - 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 ) - end - - - def content_length( header ) - unless str = header[ 'content-length' ] then - raise HTTPBadResponce, "content-length not given" - end - unless /\Acontent-length:\s*(\d+)/i === str then - raise HTTPBadResponce, "content-length format error" - end - $1.to_i - end - - def keep_alive?( header ) - if str = header[ 'connection' ] then - if /\Aconnection:\s*keep-alive/i === str then - return true end - else - if @http_version == '1.1' then - return true - end - end - false - end - - def chunked?( header ) - if str = header[ 'transfer-encoding' ] then - if /\Atransfer-encoding:\s*chunked/i === str then - return true - end - 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 HTTPBadResponce, "chunk size not given" + + def do_finish + unless @command.error_occured or @socket.closed? then + head '/', { 'Connection' => 'Close' } + end + end + + + def edit_path( path ) + path + end + + class << self + def Proxy( p_addr, p_port ) + klass = super + klass.module_eval %- + def edit_path( path ) + 'http://' + address + + (@port == #{self.port} ? '' : ':' + @port.to_s) + path + end + - + klass 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 + HTTPSession = HTTP + + + class HTTPCommand < Command + + HTTPVersion = '1.1' + + def initialize( sock ) + @http_version = HTTPVersion + + @in_header = {} + @in_header[ 'Host' ] = sock.addr + @in_header[ 'Connection' ] = 'keep-alive' + @in_header[ 'Accept' ] = '*/*' + + super sock + end + + + attr :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 + @socket.read content_length( header ), ret + end + + header + end + + + def head( path, u_header = nil ) + get_response sprintf( 'HEAD %s HTTP/%s', path, HTTPVersion ), u_header + end + + + # def put + + # def delete + + # def trace + + # def options + + + private + + + def do_quit + unless @socket.closed? then + @socket.close + end + end + + def get_response( line, u_header ) + @socket.writeline line + write_header u_header + rep = get_reply + header = read_header + reply_must rep, SuccessCode + + header + end + + def get_reply + str = @socket.readline + unless /\AHTTP\/(\d+\.\d+)?\s+(\d\d\d)\s*(.*)\z/i === str then + raise HTTPBadResponse, "wrong status line format: #{str}" + end + @http_version = $1 + status = $2 + discrip = $3 + + klass = case status[0] + when ?1 then + case status[2] + when ?0 then ContinueCode + when ?1 then SuccessCode + 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 ) + end + + + def content_length( header ) + unless str = header[ 'content-length' ] then + raise HTTPBadResponce, "content-length not given" + end + unless /\Acontent-length:\s*(\d+)/i === str then + raise HTTPBadResponce, "content-length format error" + end + $1.to_i + end + + def chunked?( header ) + if str = header[ 'transfer-encoding' ] then + if /\Atransfer-encoding:\s*chunked/i === str then + return true + end + 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 HTTPBadResponce, "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 end # module Net diff --git a/lib/net/pop.rb b/lib/net/pop.rb index 74c0e1815b..bf53984db6 100644 --- a/lib/net/pop.rb +++ b/lib/net/pop.rb @@ -4,8 +4,8 @@ written by Minero Aoki -This library is distributed under the terms of Ruby license. -You can freely distribute/modify this file. +This library is distributed under the terms of the Ruby license. +You can freely distribute/modify this library. =end @@ -19,17 +19,17 @@ module Net =begin -== Net::POP3Session +== Net::POP3 === Super Class -Net::Session +Net::Protocol === Class Methods : new( address = 'localhost', port = 110 ) - This method create a new POP3Session object. + This method create a new POP3 object. This will not open connection yet. @@ -37,11 +37,11 @@ Net::Session : start( account, password ) - This method start POP session. + This method start POP3. : each{|popmail| ...} - This method is equals to "pop3session.mails.each" + This method is equals to "pop3.mails.each" : mails @@ -50,12 +50,19 @@ Net::Session =end - class POP3Session < Session + class POP3 < Protocol - Version = '1.1.2' + Version = '1.1.3' - session_setvar :port, '110' - session_setvar :command_type, 'Net::POP3Command' + protocol_param :port, '110' + protocol_param :command_type, '::Net::POP3Command' + + protocol_param :mail_type, '::Net::POPMail' + + def initialize( addr = nil, port = nil ) + super + @mails = [].freeze + end attr :mails @@ -68,25 +75,23 @@ Net::Session private - def proto_initialize - @mails = [].freeze - end - def do_start( acnt, pwd ) - @proto.auth( acnt, pwd ) + @command.auth( acnt, pwd ) + t = self.type.mail_type @mails = [] - @proto.list.each_with_index do |size,idx| + @command.list.each_with_index do |size,idx| if size then - @mails.push POPMail.new( idx, size, @proto ) + @mails.push t.new( idx, size, @command ) end end @mails.freeze end - end # POP3Session + end - POPSession = POP3Session - POP3 = POP3Session + POP = POP3 + POPSession = POP3 + POP3Session = POP3 =begin @@ -133,10 +138,10 @@ Object class POPMail - def initialize( idx, siz, pro ) - @num = idx - @size = siz - @proto = pro + def initialize( n, s, cmd ) + @num = n + @size = s + @command = cmd @deleted = false end @@ -145,13 +150,13 @@ Object attr :size def all( dest = '' ) - @proto.retr( @num, dest ) + @command.retr( @num, dest ) end alias pop all alias mail all def top( lines, dest = '' ) - @proto.top( @num, lines, dest ) + @command.top( @num, lines, dest ) end def header( dest = '' ) @@ -159,7 +164,7 @@ Object end def delete - @proto.dele( @num ) + @command.dele( @num ) @deleted = true end alias delete! delete @@ -169,7 +174,7 @@ Object end def uidl - @proto.uidl @num + @command.uidl @num end end @@ -177,30 +182,30 @@ Object =begin -== Net::APOP3Session +== Net::APOP This class has no new methods. Only way of authetication is changed. === Super Class -Net::POP3Session +Net::POP3 =end - class APOPSession < POP3Session + class APOP < POP3 - session_setvar :command_type, 'Net::APOPCommand' + protocol_param :command_type, 'Net::APOPCommand' end - APOP = APOPSession + APOPSession = APOP =begin == Net::POP3Command -POP3 protocol class. +POP3 command class. === Super Class @@ -240,7 +245,7 @@ Net::Command : quit - This method finishes POP3 session. + This method ends POP using 'QUIT' commmand. : rset @@ -276,7 +281,7 @@ Net::Command def auth( acnt, pass ) - @socket.writeline( 'USER ' + acnt ) + @socket.writeline 'USER ' + acnt check_reply_auth @socket.writeline( 'PASS ' + pass ) @@ -287,8 +292,7 @@ Net::Command def list - @socket.writeline( 'LIST' ) - check_reply( SuccessCode ) + getok 'LIST' arr = [] @socket.read_pendlist do |line| @@ -301,36 +305,29 @@ Net::Command def rset - @socket.writeline( 'RSET' ) - check_reply( SuccessCode ) + getok 'RSET' end def top( num, lines = 0, dest = '' ) - @socket.writeline( sprintf( 'TOP %d %d', num, lines ) ) - check_reply( SuccessCode ) - - return @socket.read_pendstr( dest ) + getok sprintf( 'TOP %d %d', num, lines ) + @socket.read_pendstr( dest ) end def retr( num, dest = '', &block ) - @socket.writeline( sprintf( 'RETR %d', num ) ) - check_reply( SuccessCode ) - - return @socket.read_pendstr( dest, &block ) + getok sprintf( 'RETR %d', num ) + @socket.read_pendstr( dest, &block ) end def dele( num ) - @socket.writeline( 'DELE ' + num.to_s ) - check_reply( SuccessCode ) + getok sprintf( 'DELE %d', num ) end def uidl( num ) - @socket.writeline( 'UIDL ' + num.to_s ) - rep = check_reply( SuccessCode ) + rep = getok( sprintf 'UIDL %d', num ) uid = rep.msg.split(' ')[1] uid @@ -341,8 +338,7 @@ Net::Command def do_quit - @socket.writeline( 'QUIT' ) - check_reply( SuccessCode ) + getok 'QUIT' end diff --git a/lib/net/session.rb b/lib/net/session.rb index ec116322f6..87e9d67a7a 100644 --- a/lib/net/session.rb +++ b/lib/net/session.rb @@ -1,11 +1,11 @@ =begin -= net/session.rb version 1.1.2 += net/session.rb version 1.1.3 written by Minero Aoki -This library is distributed under the terms of Ruby style license. -You can freely distribute/modify this file. +This library is distributed under the terms of the Ruby license. +You can freely distribute/modify this library. =end @@ -18,9 +18,9 @@ module Net =begin -== Net::Session +== Net::Protocol -the abstruct class for Internet protocol session +the abstruct class for Internet protocol === Super Class @@ -30,22 +30,27 @@ Object : Version - The version of Session class. It is a string like "1.1.2". + The version of Session class. It is a string like "1.1.3". === Class Methods : new( address = 'localhost', port = nil ) - This method Create a new Session object. + This method Creates a new Session object. : start( address = 'localhost', port = nil, *args ) : start( address = 'localhost', port = nil, *args ){|session| .... } - This method create a new Session object and start session. + This method creates a new Session object and start session. If you call this method with block, Session object give itself to block and finish session when block returns. +: Proxy( address, port ) + + This method creates a proxy class of its protocol. + Arguments are address/port of proxy host. + === Methods @@ -59,14 +64,14 @@ Object : start( *args ) - This method start session. If you call this method when the session + This method start protocol. If you call this method when the protocol is already started, this only returns false without doing anything. '*args' are specified in subclasses. : finish - This method finish session. If you call this method before session starts, + This method ends protocol. If you call this method before protocol starts, it only return false without doing anything. : active? @@ -75,20 +80,20 @@ Object =end - class Session + class Protocol - Version = '1.1.2' + Version = '1.1.3' class << self def start( address = 'localhost', port = nil, *args ) - session = new( address, port ) + instance = new( address, port ) if iterator? then - session.start( *args ) { yield session } + instance.start( *args ) { yield instance } else - session.start *args - session + instance.start *args + instance end end @@ -104,11 +109,8 @@ Object @port = port end - def connect - tmpa, tmpp = @address, @port - @address, @port = @proxyaddr, @proxyport - super - @address, @port = tmpa, tmpp + def connect( addr, port ) + super @proxyaddr, @proxyport end private :connect @@ -129,7 +131,7 @@ Object private - def session_setvar( name, val ) + def protocol_param( name, val ) module_eval %- def self.#{name.id2name} #{val} @@ -143,26 +145,26 @@ Object # # sub-class requirements # - # session_setvar command_type - # session_setvar port + # protocol_param command_type + # protocol_param port # # private method do_start (optional) # private method do_finish (optional) # - session_setvar :port, 'nil' - session_setvar :command_type, 'nil' - session_setvar :socket_type, 'Net::ProtocolSocket' + protocol_param :port, 'nil' + protocol_param :command_type, 'nil' + protocol_param :socket_type, '::Net::ProtocolSocket' - def initialize( addr = 'localhost', port = nil ) - @address = addr + def initialize( addr = nil, port = nil ) + @address = addr || 'localhost' @port = port || self.type.port @active = false @pipe = nil - @proto = nil + @command = nil @socket = nil end @@ -170,6 +172,7 @@ Object attr :address attr :port + attr :command attr :socket @@ -178,7 +181,7 @@ Object @active = true begin - connect + connect @address, @port do_start *args yield if iterator? ensure @@ -187,7 +190,7 @@ Object end def finish - if @proto then + if @command then do_finish disconnect end @@ -225,19 +228,21 @@ Object end - def connect - @socket = self.type.socket_type.open( @address, @port, @pipe ) - @proto = self.type.command_type.new( @socket ) + def connect( addr, port ) + @socket = self.type.socket_type.open( addr, port, @pipe ) + @command = self.type.command_type.new( @socket ) end def disconnect - @proto.quit - @proto = nil - @socket = nil + @command.quit + @command = nil + @socket = nil end end + Session = Protocol + =begin @@ -259,7 +264,7 @@ Object : quit - This method finishes protocol. + This method dispatch command which ends the protocol. =end @@ -301,6 +306,11 @@ Object @error_occured = true rep.error! @socket.sending end + + def getok( line, ok = SuccessCode ) + @socket.writeline line + check_reply ok + end end diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb index b0f9b54b9a..339ee41e6d 100644 --- a/lib/net/smtp.rb +++ b/lib/net/smtp.rb @@ -4,8 +4,8 @@ written by Minero Aoki -This library is distributed under the terms of Ruby license. -You can freely distribute/modify this file. +This library is distributed under the terms of the Ruby license. +You can freely distribute/modify this library. =end @@ -18,57 +18,57 @@ module Net =begin -== Net::SMTPSession +== Net::SMTP === Super Class -Net::Session +Net::Protocol === Class Methods : new( address = 'localhost', port = 25 ) - This method create new SMTPSession object. + This method create new SMTP object. === Methods : start( helo_domain = ENV['HOSTNAME'] ) - This method opens TCP connection and start SMTP session. - If session had been started, do nothing and return false. + This method opens TCP connection and start SMTP. + If protocol had been started, do nothing and return false. : sendmail( mailsrc, from_domain, to_addrs ) This method sends 'mailsrc' as mail. SMTPSession read strings from 'mailsrc' by calling 'each' iterator, and convert them into "\r\n" terminated string when write. - SMTPSession's Exceptions are: - * Protocol::ProtoSyntaxError: syntax error (errno.500) - * Protocol::ProtoFatalError: fatal error (errno.550) - * Protocol::ProtoUnknownError: unknown error - * Protocol::ProtoServerBusy: temporary error (errno.420/450) + 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) : finish - This method closes SMTP session. - If session had not started, do nothind and return false. + This method ends SMTP. + If protocol had not started, do nothind and return false. =end - class SMTPSession < Session + class SMTP < Protocol - Version = '1.1.2' + Version = '1.1.3' - session_setvar :port, '25' - session_setvar :command_type, 'Net::SMTPCommand' + protocol_param :port, '25' + protocol_param :command_type, '::Net::SMTPCommand' def sendmail( mailsrc, fromaddr, toaddrs ) - @proto.mailfrom fromaddr - @proto.rcpt toaddrs - @proto.data - @proto.sendmail mailsrc + @command.mailfrom fromaddr + @command.rcpt toaddrs + @command.data + @command.sendmail mailsrc end @@ -79,12 +79,12 @@ Net::Session unless helodom then raise ArgumentError, "cannot get hostname" end - @proto.helo helodom + @command.helo helodom end end - SMTP = SMTPSession + SMTPSession = SMTP =begin @@ -99,14 +99,14 @@ Net::Command : new( socket ) - This method creates new SMTPCommand object, and open SMTP session. + This method creates new SMTPCommand object, and open SMTP. === Methods : helo( helo_domain ) - This method send "HELO" command and start SMTP session. + This method send "HELO" command and start SMTP. helo_domain is localhost's FQDN. : mailfrom( from_addr ) @@ -139,34 +139,30 @@ Net::Command def helo( fromdom ) - @socket.writeline( 'HELO ' << fromdom ) - check_reply( SuccessCode ) + getok sprintf( 'HELO %s', fromdom ) end def mailfrom( fromaddr ) - @socket.writeline( 'MAIL FROM:<' + fromaddr + '>' ) - check_reply( SuccessCode ) + getok sprintf( 'MAIL FROM:<%s>', fromaddr ) end def rcpt( toaddrs ) toaddrs.each do |i| - @socket.writeline( 'RCPT TO:<' + i + '>' ) - check_reply( SuccessCode ) + getok sprintf( 'RCPT TO:<%s>', i ) end end def data - @socket.writeline( 'DATA' ) - check_reply( ContinueCode ) + getok 'DATA', ContinueCode end def writemail( mailsrc ) - @socket.write_pendstr( mailsrc ) - check_reply( SuccessCode ) + @socket.write_pendstr mailsrc + check_reply SuccessCode end alias sendmail writemail @@ -175,8 +171,7 @@ Net::Command def do_quit - @socket.writeline( 'QUIT' ) - check_reply( SuccessCode ) + getok 'QUIT' end @@ -184,19 +179,19 @@ Net::Command arr = read_reply stat = arr[0][0,3] - cls = UnknownCode - case stat[0] - when ?2 then cls = SuccessCode - when ?3 then cls = ContinueCode - when ?4 then cls = ServerBusyCode - when ?5 then - case stat[1] - when ?0 then cls = SyntaxErrorCode - when ?5 then cls = FatalErrorCode - end - end + klass = UnknownCode + klass = case stat[0] + when ?2 then SuccessCode + when ?3 then ContinueCode + when ?4 then ServerBusyCode + when ?5 then + case stat[1] + when ?0 then SyntaxErrorCode + when ?5 then FatalErrorCode + end + end - return cls.new( stat, arr.join('') ) + klass.new( stat, arr.join('') ) end