summaryrefslogtreecommitdiff
path: root/lib/net
diff options
context:
space:
mode:
author(no author) <(no author)@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2000-04-14 14:35:50 +0000
committer(no author) <(no author)@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2000-04-14 14:35:50 +0000
commited131ebad88c2948d7b40f93ff6ce39f1eb8a2f7 (patch)
tree7bbf62c5e0d0bb8ff971637cf432c6f16824f8f6 /lib/net
parentbe1fea072cd0d22788ef8a931c0c6b64a2503b5d (diff)
This commit was manufactured by cvs2svn to create tag 'v1_4_4'.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/tags/v1_4_4@668 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/net')
-rw-r--r--lib/net/ftp.rb642
-rw-r--r--lib/net/http.rb312
-rw-r--r--lib/net/pop.rb315
-rw-r--r--lib/net/protocol.rb724
-rw-r--r--lib/net/session.rb565
-rw-r--r--lib/net/smtp.rb178
-rw-r--r--lib/net/telnet.rb749
7 files changed, 3222 insertions, 263 deletions
diff --git a/lib/net/ftp.rb b/lib/net/ftp.rb
new file mode 100644
index 0000000000..cbb2832a0d
--- /dev/null
+++ b/lib/net/ftp.rb
@@ -0,0 +1,642 @@
+=begin
+
+= net/ftp.rb
+
+written by Shugo Maeda <shugo@ruby-lang.org>
+
+This library is distributed under the terms of the Ruby license.
+You can freely distribute/modify this library.
+
+=end
+
+require "socket"
+require "monitor"
+
+module Net
+
+ class FTPError < StandardError; end
+ class FTPReplyError < FTPError; end
+ class FTPTempError < FTPError; end
+ class FTPPermError < FTPError; end
+ class FTPProtoError < FTPError; end
+
+ class FTP
+ include MonitorMixin
+
+ FTP_PORT = 21
+ CRLF = "\r\n"
+
+ attr_accessor :passive, :return_code, :debug_mode
+ attr_reader :welcome, :lastresp
+
+ def FTP.open(host, user = nil, passwd = nil, acct = nil)
+ new(host, user, passwd, acct)
+ end
+
+ def initialize(host = nil, user = nil, passwd = nil, acct = nil)
+ super
+ @passive = false
+ @return_code = "\n"
+ @debug_mode = false
+ if host
+ connect(host)
+ if user
+ login(user, passwd, acct)
+ end
+ end
+ end
+
+ def open_socket(host, port)
+ if defined? SOCKSsocket and ENV["SOCKS_SERVER"]
+ @passive = true
+ return SOCKSsocket.open(host, port)
+ else
+ return TCPsocket.open(host, port)
+ end
+ end
+ private :open_socket
+
+ def connect(host, port = FTP_PORT)
+ if @debug_mode
+ print "connect: ", host, ", ", port, "\n"
+ end
+ synchronize do
+ @sock = open_socket(host, port)
+ voidresp
+ end
+ end
+
+ def sanitize(s)
+ if s =~ /^PASS /i
+ return s[0, 5] + "*" * (s.length - 5)
+ else
+ return s
+ end
+ end
+ private :sanitize
+
+ def putline(line)
+ if @debug_mode
+ print "put: ", sanitize(line), "\n"
+ end
+ line = line + CRLF
+ @sock.write(line)
+ end
+ private :putline
+
+ def getline
+ line = @sock.readline # if get EOF, raise EOFError
+ if line[-2, 2] == CRLF
+ line = line[0 .. -3]
+ elsif line[-1] == ?\r or
+ line[-1] == ?\n
+ line = line[0 .. -2]
+ end
+ if @debug_mode
+ print "get: ", sanitize(line), "\n"
+ end
+ return line
+ end
+ private :getline
+
+ def getmultiline
+ line = getline
+ buff = line
+ if line[3] == ?-
+ code = line[0, 3]
+ begin
+ line = getline
+ buff << "\n" << line
+ end until line[0, 3] == code and line[3] != ?-
+ end
+ return buff << "\n"
+ end
+ private :getmultiline
+
+ def getresp
+ resp = getmultiline
+ @lastresp = resp[0, 3]
+ c = resp[0]
+ case c
+ when ?1, ?2, ?3
+ return resp
+ when ?4
+ raise FTPTempError, resp
+ when ?5
+ raise FTPPermError, resp
+ else
+ raise FTPProtoError, resp
+ end
+ end
+ private :getresp
+
+ def voidresp
+ resp = getresp
+ if resp[0] != ?2
+ raise FTPReplyError, resp
+ end
+ end
+ private :voidresp
+
+ def sendcmd(cmd)
+ synchronize do
+ putline(cmd)
+ return getresp
+ end
+ end
+
+ def voidcmd(cmd)
+ synchronize do
+ putline(cmd)
+ voidresp
+ end
+ end
+
+ def sendport(host, port)
+ af = (@sock.peeraddr)[0]
+ if af == "AF_INET"
+ hbytes = host.split(".")
+ pbytes = [port / 256, port % 256]
+ bytes = hbytes + pbytes
+ cmd = "PORT " + bytes.join(",")
+ elsif af == "AF_INET6"
+ cmd = "EPRT |2|" + host + "|" + sprintf("%d", port) + "|"
+ else
+ raise FTPProtoError, host
+ end
+ voidcmd(cmd)
+ end
+ private :sendport
+
+ def makeport
+ sock = TCPserver.open(@sock.addr[3], 0)
+ port = sock.addr[1]
+ host = TCPsocket.getaddress(@sock.addr[2])
+ resp = sendport(host, port)
+ return sock
+ end
+ private :makeport
+
+ def makepasv
+ if @sock.peeraddr[0] == "AF_INET"
+ host, port = parse227(sendcmd("PASV"))
+ else
+ host, port = parse229(sendcmd("EPSV"))
+ # host, port = parse228(sendcmd("LPSV"))
+ end
+ return host, port
+ end
+ private :makepasv
+
+ def transfercmd(cmd)
+ if @passive
+ host, port = makepasv
+ conn = open_socket(host, port)
+ resp = sendcmd(cmd)
+ if resp[0] != ?1
+ raise FTPReplyError, resp
+ end
+ else
+ sock = makeport
+ resp = sendcmd(cmd)
+ if resp[0] != ?1
+ raise FTPReplyError, resp
+ end
+ conn = sock.accept
+ end
+ return conn
+ end
+ private :transfercmd
+
+ def getaddress
+ thishost = Socket.gethostname
+ if not thishost.index(".")
+ thishost = Socket.gethostbyname(thishost)[0]
+ end
+ if ENV.has_key?("LOGNAME")
+ realuser = ENV["LOGNAME"]
+ elsif ENV.has_key?("USER")
+ realuser = ENV["USER"]
+ else
+ realuser = "anonymous"
+ end
+ return realuser + "@" + thishost
+ end
+ private :getaddress
+
+ def login(user = "anonymous", passwd = nil, acct = nil)
+ if user == "anonymous" and passwd == nil
+ passwd = getaddress
+ end
+
+ resp = ""
+ synchronize do
+ resp = sendcmd('USER ' + user)
+ if resp[0] == ?3
+ resp = sendcmd('PASS ' + passwd)
+ end
+ if resp[0] == ?3
+ resp = sendcmd('ACCT ' + acct)
+ end
+ end
+ if resp[0] != ?2
+ raise FTPReplyError, resp
+ end
+ @welcome = resp
+ end
+
+ def retrbinary(cmd, blocksize, callback = Proc.new)
+ synchronize do
+ voidcmd("TYPE I")
+ conn = transfercmd(cmd)
+ loop do
+ data = conn.read(blocksize)
+ break if data == nil
+ callback.call(data)
+ end
+ conn.close
+ voidresp
+ end
+ end
+
+ def retrlines(cmd, callback = nil)
+ if iterator?
+ callback = Proc.new
+ elsif not callback.is_a?(Proc)
+ callback = Proc.new {|line| print line, "\n"}
+ end
+ synchronize do
+ voidcmd("TYPE A")
+ conn = transfercmd(cmd)
+ loop do
+ line = conn.gets
+ break if line == nil
+ if line[-2, 2] == CRLF
+ line = line[0 .. -3]
+ elsif line[-1] == ?\n
+ line = line[0 .. -2]
+ end
+ callback.call(line)
+ end
+ conn.close
+ voidresp
+ end
+ end
+
+ def storbinary(cmd, file, blocksize, callback = nil)
+ if iterator?
+ callback = Proc.new
+ end
+ use_callback = callback.is_a?(Proc)
+ synchronize do
+ voidcmd("TYPE I")
+ conn = transfercmd(cmd)
+ loop do
+ buf = file.read(blocksize)
+ break if buf == nil
+ conn.write(buf)
+ callback.call(buf) if use_callback
+ end
+ conn.close
+ voidresp
+ end
+ end
+
+ def storlines(cmd, file, callback = nil)
+ if iterator?
+ callback = Proc.new
+ end
+ use_callback = callback.is_a?(Proc)
+ synchronize do
+ voidcmd("TYPE A")
+ conn = transfercmd(cmd)
+ loop do
+ buf = file.gets
+ break if buf == nil
+ if buf[-2, 2] != CRLF
+ buf = buf.chomp + CRLF
+ end
+ conn.write(buf)
+ callback.call(buf) if use_callback
+ end
+ conn.close
+ voidresp
+ end
+ end
+
+ def getbinaryfile(remotefile, localfile, blocksize, callback = nil)
+ if iterator?
+ callback = Proc.new
+ end
+ use_callback = callback.is_a?(Proc)
+ f = open(localfile, "w")
+ begin
+ f.binmode
+ retrbinary("RETR " + remotefile, blocksize) do |data|
+ f.write(data)
+ callback.call(data) if use_callback
+ end
+ ensure
+ f.close
+ end
+ end
+
+ def gettextfile(remotefile, localfile, callback = nil)
+ if iterator?
+ callback = Proc.new
+ end
+ use_callback = callback.is_a?(Proc)
+ f = open(localfile, "w")
+ begin
+ retrlines("RETR " + remotefile) do |line|
+ line = line + @return_code
+ f.write(line)
+ callback.call(line) if use_callback
+ end
+ ensure
+ f.close
+ end
+ end
+
+ def putbinaryfile(localfile, remotefile, blocksize, callback = nil)
+ if iterator?
+ callback = Proc.new
+ end
+ use_callback = callback.is_a?(Proc)
+ f = open(localfile)
+ begin
+ f.binmode
+ storbinary("STOR " + remotefile, f, blocksize) do |data|
+ callback.call(data) if use_callback
+ end
+ ensure
+ f.close
+ end
+ end
+
+ def puttextfile(localfile, remotefile, callback = nil)
+ if iterator?
+ callback = Proc.new
+ end
+ use_callback = callback.is_a?(Proc)
+ f = open(localfile)
+ begin
+ storlines("STOR " + remotefile, f) do |line|
+ callback.call(line) if use_callback
+ end
+ ensure
+ f.close
+ end
+ end
+
+ def acct(account)
+ cmd = "ACCT " + account
+ voidcmd(cmd)
+ end
+
+ def nlst(dir = nil)
+ cmd = "NLST"
+ if dir
+ cmd = cmd + " " + dir
+ end
+ files = []
+ retrlines(cmd) do |line|
+ files.push(line)
+ end
+ return files
+ end
+
+ def list(*args, &block)
+ cmd = "LIST"
+ args.each do |arg|
+ cmd = cmd + " " + arg
+ end
+ if block
+ retrlines(cmd, &block)
+ else
+ lines = []
+ retrlines(cmd) do |line|
+ lines << line
+ end
+ return lines
+ end
+ end
+ alias ls list
+ alias dir list
+
+ def rename(fromname, toname)
+ resp = sendcmd("RNFR " + fromname)
+ if resp[0] != ?3
+ raise FTPReplyError, resp
+ end
+ voidcmd("RNTO " + toname)
+ end
+
+ def delete(filename)
+ resp = sendcmd("DELE " + filename)
+ if resp[0, 3] == "250"
+ return
+ elsif resp[0] == ?5
+ raise FTPPermError, resp
+ else
+ raise FTPReplyError, resp
+ end
+ end
+
+ def chdir(dirname)
+ if dirname == ".."
+ begin
+ voidcmd("CDUP")
+ return
+ rescue FTPPermError
+ if $![0, 3] != "500"
+ raise FTPPermError, $!
+ end
+ end
+ end
+ cmd = "CWD " + dirname
+ voidcmd(cmd)
+ end
+
+ def size(filename)
+ voidcmd("TYPE I")
+ resp = sendcmd("SIZE " + filename)
+ if resp[0, 3] != "213"
+ raise FTPReplyError, resp
+ end
+ return resp[3..-1].strip.to_i
+ end
+
+ MDTM_REGEXP = /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/
+
+ def mtime(filename, local = false)
+ str = mdtm(filename)
+ ary = str.scan(MDTM_REGEXP)[0].collect {|i| i.to_i}
+ return local ? Time.local(*ary) : Time.gm(*ary)
+ end
+
+ def mkdir(dirname)
+ resp = sendcmd("MKD " + dirname)
+ return parse257(resp)
+ end
+
+ def rmdir(dirname)
+ voidcmd("RMD " + dirname)
+ end
+
+ def pwd
+ resp = sendcmd("PWD")
+ return parse257(resp)
+ end
+ alias getdir pwd
+
+ def system
+ resp = sendcmd("SYST")
+ if resp[0, 3] != "215"
+ raise FTPReplyError, resp
+ end
+ return resp[4 .. -1]
+ end
+
+ def abort
+ line = "ABOR" + CRLF
+ print "put: ABOR\n" if @debug_mode
+ @sock.send(line, Socket::MSG_OOB)
+ resp = getmultiline
+ unless ["426", "226", "225"].include?(resp[0, 3])
+ raise FTPProtoError, resp
+ end
+ return resp
+ end
+
+ def status
+ line = "STAT" + CRLF
+ print "put: STAT\n" if @debug_mode
+ @sock.send(line, Socket::MSG_OOB)
+ return getresp
+ end
+
+ def mdtm(filename)
+ resp = sendcmd("MDTM " + filename)
+ if resp[0, 3] == "213"
+ return resp[3 .. -1].strip
+ end
+ end
+
+ def help(arg = nil)
+ cmd = "HELP"
+ if arg
+ cmd = cmd + " " + arg
+ end
+ sendcmd(cmd)
+ end
+
+ def quit
+ voidcmd("QUIT")
+ end
+
+ def close
+ @sock.close if @sock and not @sock.closed?
+ end
+
+ def closed?
+ @sock == nil or @sock.closed?
+ end
+
+ def parse227(resp)
+ if resp[0, 3] != "227"
+ raise FTPReplyError, resp
+ end
+ left = resp.index("(")
+ right = resp.index(")")
+ if left == nil or right == nil
+ raise FTPProtoError, resp
+ end
+ numbers = resp[left + 1 .. right - 1].split(",")
+ if numbers.length != 6
+ raise FTPProtoError, resp
+ end
+ host = numbers[0, 4].join(".")
+ port = (numbers[4].to_i << 8) + numbers[5].to_i
+ return host, port
+ end
+ private :parse227
+
+ def parse228(resp)
+ if resp[0, 3] != "228"
+ raise FTPReplyError, resp
+ end
+ left = resp.index("(")
+ right = resp.index(")")
+ if left == nil or right == nil
+ raise FTPProtoError, resp
+ end
+ numbers = resp[left + 1 .. right - 1].split(",")
+ if numbers[0] == "4"
+ if numbers.length != 9 || numbers[1] != "4" || numbers[2 + 4] != "2"
+ raise FTPProtoError, resp
+ end
+ host = numbers[2, 4].join(".")
+ port = (numbers[7].to_i << 8) + numbers[8].to_i
+ elsif numbers[0] == "6"
+ if numbers.length != 21 || numbers[1] != "16" || numbers[2 + 16] != "2"
+ raise FTPProtoError, resp
+ end
+ v6 = ["", "", "", "", "", "", "", ""]
+ for i in 0 .. 7
+ v6[i] = sprintf("%02x%02x", numbers[(i * 2) + 2].to_i,
+ numbers[(i * 2) + 3].to_i)
+ end
+ host = v6[0, 8].join(":")
+ port = (numbers[19].to_i << 8) + numbers[20].to_i
+ end
+ return host, port
+ end
+ private :parse228
+
+ def parse229(resp)
+ if resp[0, 3] != "229"
+ raise FTPReplyError, resp
+ end
+ left = resp.index("(")
+ right = resp.index(")")
+ if left == nil or right == nil
+ raise FTPProtoError, resp
+ end
+ numbers = resp[left + 1 .. right - 1].split(resp[left + 1, 1])
+ if numbers.length != 4
+ raise FTPProtoError, resp
+ end
+ port = numbers[3].to_i
+ host = (@sock.peeraddr())[3]
+ return host, port
+ end
+ private :parse228
+
+ def parse257(resp)
+ if resp[0, 3] != "257"
+ raise FTPReplyError, resp
+ end
+ if resp[3, 2] != ' "'
+ return ""
+ end
+ dirname = ""
+ i = 5
+ n = resp.length
+ while i < n
+ c = resp[i, 1]
+ i = i + 1
+ if c == '"'
+ if i > n or resp[i, 1] != '"'
+ break
+ end
+ i = i + 1
+ end
+ dirname = dirname + c
+ end
+ return dirname
+ end
+ private :parse257
+ end
+
+end
diff --git a/lib/net/http.rb b/lib/net/http.rb
new file mode 100644
index 0000000000..4847f88bf7
--- /dev/null
+++ b/lib/net/http.rb
@@ -0,0 +1,312 @@
+=begin
+
+= net/http.rb
+
+maintained by Minero Aoki <aamine@dp.u-netsurf.ne.jp>
+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'
+
+
+module Net
+
+
+class HTTPError < ProtocolError; end
+class HTTPBadResponse < HTTPError; end
+
+
+=begin
+
+= HTTP class
+
+== Class Methods
+
+: new( address, port = 80 )
+ 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
+
+ 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
+
+
+ private
+
+
+ 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
+ end
+
+ false
+ end
+
+
+ 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
+ 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
+ if clen = content_length( header ) then
+ @socket.read clen, ret
+ else
+ @socket.read_all ret
+ end
+ 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
+ return nil
+ end
+ unless /\Acontent-length:\s*(\d+)/i === str then
+ raise HTTPBadResponse, "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 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
+
+
+end # module Net
diff --git a/lib/net/pop.rb b/lib/net/pop.rb
index bd4551571d..0ec2481557 100644
--- a/lib/net/pop.rb
+++ b/lib/net/pop.rb
@@ -1,8 +1,14 @@
-#
-# pop.rb version 1.0.1
-#
-# author: Minero Aoki <aamine@dp.u-netsurf.ne.jp>
-#
+=begin
+
+= net/pop.rb
+
+written by Minero Aoki <aamine@dp.u-netsurf.ne.jp>
+
+This library is distributed under the terms of the Ruby license.
+You can freely distribute/modify this library.
+
+=end
+
require 'net/session'
require 'md5'
@@ -10,101 +16,250 @@ require 'md5'
module Net
- class POP3Session < Session
- attr :mails
+=begin
- def each() @mails.each{|m| yield m} end
+== Net::POP3
+=== Super Class
- private
+Net::Protocol
+
+=== Class Methods
+
+: new( address = 'localhost', port = 110 )
+ This method create a new POP3 object.
+ This will not open connection yet.
+
+
+=== Methods
+
+: start( account, password )
+ This method start POP3.
+
+: each{|popmail| ...}
+ This method is equals to "pop3.mails.each"
+: mails
+ This method returns an array of ((URL:#POPMail)).
+ This array is renewed when login.
- def proto_initialize
- @proto_type = POP3Command
- @port = 110
- @mails = [].freeze
+=end
+
+ class POP3 < Protocol
+
+ 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
+
+ def each
+ @mails.each {|m| yield m }
+ end
+
+
+ private
+
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
- def do_finish
- @proto.quit
- end
+ POP = POP3
+ POPSession = POP3
+ POP3Session = POP3
+=begin
- class POPMail
+== Net::POPMail
- def initialize( idx, siz, pro )
- @num = idx
- @size = siz
- @proto = pro
+A class of mail which exists on POP server.
- @deleted = false
- end
+=== Super Class
- attr :size
+Object
- def all( dest = '' )
- @proto.retr( @num, dest )
- end
- alias pop all
- alias mail all
- def top( lines, dest = '' )
- @proto.top( @num, lines, dest )
- end
+=== Method
- def header( dest = '' )
- top( 0, dest )
- end
+: all
+: pop
+: mail
+ This method fetches a mail and return it.
- def delete
- @proto.dele( @num )
- @deleted = true
- end
- alias delete! delete
+: header
+ This method fetches only mail header.
- def deleted?
- @deleted
- end
+: top( lines )
+ This method fetches mail header and 'lines' lines body.
+
+: delete
+: delete!
+ This method deletes mail.
+
+: size
+ size of mail(bytes)
+
+: deleted?
+ true if mail was deleted
+
+=end
+
+ class POPMail
+
+ def initialize( n, s, cmd )
+ @num = n
+ @size = s
+ @command = cmd
+ @deleted = false
end
- end
+ attr :size
- class APOPSession < POP3Session
+ def all( dest = '' )
+ @command.retr( @num, dest )
+ end
+ alias pop all
+ alias mail all
- def proto_initialize
- super
- @proto_type = APOPCommand
+ def top( lines, dest = '' )
+ @command.top( @num, lines, dest )
+ end
+
+ def header( dest = '' )
+ top( 0, dest )
+ end
+
+ def delete
+ @command.dele( @num )
+ @deleted = true
+ end
+ alias delete! delete
+
+ def deleted?
+ @deleted
end
+ def uidl
+ @command.uidl @num
+ end
+
+ end
+
+
+=begin
+
+== Net::APOP
+
+This class has no new methods. Only way of authetication is changed.
+
+=== Super Class
+
+Net::POP3
+
+=end
+
+ class APOP < POP3
+
+ protocol_param :command_type, 'Net::APOPCommand'
+
end
+ 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
- POPSession = POP3Session
- POP3 = POP3Session
+ [ 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
+ def initialize( sock )
+ super
+ check_reply SuccessCode
+ end
+
+
def auth( acnt, pass )
- @socket.writeline( 'USER ' + acnt )
+ @socket.writeline 'USER ' + acnt
check_reply_auth
@socket.writeline( 'PASS ' + pass )
@@ -115,8 +270,7 @@ module Net
def list
- @socket.writeline( 'LIST' )
- check_reply( SuccessCode )
+ getok 'LIST'
arr = []
@socket.read_pendlist do |line|
@@ -129,40 +283,40 @@ module Net
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( sprintf( 'DELE %s', num ) )
- check_reply( SuccessCode )
+ getok sprintf( 'DELE %d', num )
end
+ def uidl( num )
+ rep = getok( sprintf 'UIDL %d', num )
+ uid = rep.msg.split(' ')[1]
+
+ uid
+ end
+
private
def do_quit
- @socket.writeline( 'QUIT' )
- check_reply( SuccessCode )
+ getok 'QUIT'
end
@@ -190,6 +344,22 @@ module Net
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
@@ -222,9 +392,4 @@ module Net
end
-
- unless Session::Version == '1.0.1' then
- $stderr.puts "WARNING: wrong version of session.rb & pop.rb"
- end
-
end
diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb
new file mode 100644
index 0000000000..4d80153f2c
--- /dev/null
+++ b/lib/net/protocol.rb
@@ -0,0 +1,724 @@
+=begin
+
+= net/protocol.rb
+
+written by Minero Aoki <aamine@dp.u-netsurf.ne.jp>
+
+This library is distributed under the terms of the Ruby license.
+You can freely distribute/modify this library.
+
+=end
+
+
+require 'socket'
+
+
+module Net
+
+ Version = '1.1.8'
+
+=begin
+
+== Net::Protocol
+
+the abstruct class for Internet protocol
+
+=== Super Class
+
+Object
+
+=== Class Methods
+
+: new( address = 'localhost', port = nil )
+ This method Creates a new protocol object.
+
+: start( address = 'localhost', port = nil, *args )
+: start( address = 'localhost', port = nil, *args ){|proto| .... }
+ This method creates a new Protocol object and start session.
+ If you call this method with block, Protocol 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
+
+: address
+ the address of connecting server (FQDN).
+
+: port
+ connecting port number
+
+: start( *args )
+ 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 ends protocol. If you call this method before protocol starts,
+ it only return false without doing anything.
+
+: active?
+ true if session have been started
+
+=end
+
+ class Protocol
+
+ Version = ::Net::Version
+
+ class << self
+
+ def start( address = 'localhost', port = nil, *args )
+ instance = new( address, port )
+
+ if iterator? then
+ instance.start( *args ) { yield instance }
+ else
+ instance.start *args
+ instance
+ end
+ end
+
+ def Proxy( p_addr, p_port )
+ klass = Class.new( self )
+ klass.module_eval %-
+
+ def initialize( addr, port )
+ @proxyaddr = '#{p_addr}'
+ @proxyport = '#{p_port}'
+ super @proxyaddr, @proxyport
+ @address = addr
+ @port = port
+ end
+
+ def connect( addr, port )
+ super @proxyaddr, @proxyport
+ end
+ private :connect
+
+ attr_reader :proxyaddr, :proxyport
+ -
+ def klass.proxy?
+ true
+ end
+
+ klass
+ end
+
+ def proxy?
+ false
+ end
+
+
+ private
+
+ def protocol_param( name, val )
+ module_eval %-
+ def self.#{name.id2name}
+ #{val}
+ end
+ -
+ end
+
+ end
+
+
+ #
+ # sub-class requirements
+ #
+ # protocol_param command_type
+ # protocol_param port
+ #
+ # private method do_start (optional)
+ # private method do_finish (optional)
+ #
+
+ protocol_param :port, 'nil'
+ protocol_param :command_type, 'nil'
+ protocol_param :socket_type, '::Net::Socket'
+
+
+ def initialize( addr = nil, port = nil )
+ @address = addr || 'localhost'
+ @port = port || self.type.port
+
+ @active = false
+ @pipe = nil
+
+ @command = nil
+ @socket = nil
+ end
+
+
+ attr_reader :address, :port,
+ :command, :socket
+
+
+ def start( *args )
+ return false if active?
+ @active = true
+
+ begin
+ connect @address, @port
+ do_start *args
+ yield if iterator?
+ ensure
+ finish if iterator?
+ end
+ end
+
+ def finish
+ ret = active?
+
+ do_finish if @command
+ disconnect
+ @active = false
+
+ ret
+ end
+
+ def active?
+ @active
+ end
+
+ def set_pipe( arg ) # un-documented
+ @pipe = arg
+ end
+
+
+ private
+
+
+ def do_start
+ end
+
+ def do_finish
+ @command.quit
+ end
+
+
+ def connect( addr, port )
+ @socket = self.type.socket_type.open( addr, port, @pipe )
+ @command = self.type.command_type.new( @socket )
+ end
+
+ def disconnect
+ @command = nil
+ if @socket and not @socket.closed? then
+ @socket.close
+ end
+ @socket = nil
+ end
+
+ end
+
+ Session = Protocol
+
+
+
+ class Command
+
+ def initialize( sock )
+ @socket = sock
+ @error_occured = false
+ @last_reply = nil
+ end
+
+ attr_reader :socket, :error_occured, :last_reply
+ attr_writer :socket
+
+ def quit
+ if @socket and not @socket.closed? then
+ do_quit
+ @error_occured = false
+ end
+ end
+
+
+ private
+
+ def do_quit
+ end
+
+ # abstract get_reply()
+
+ def check_reply( *oks )
+ @last_reply = get_reply
+ reply_must( @last_reply, *oks )
+ end
+
+ def reply_must( rep, *oks )
+ oks.each do |i|
+ if i === rep then
+ return rep
+ end
+ end
+
+ @error_occured = true
+ rep.error! @socket.sending
+ end
+
+ def getok( line, ok = SuccessCode )
+ @socket.writeline line
+ check_reply ok
+ end
+
+ end
+
+
+ class ProtocolError < StandardError ; end
+ class ProtoSyntaxError < ProtocolError ; end
+ class ProtoFatalError < ProtocolError ; end
+ class ProtoUnknownError < ProtocolError ; end
+ class ProtoServerError < ProtocolError ; end
+ class ProtoAuthError < ProtocolError ; end
+ class ProtoCommandError < ProtocolError ; end
+ class ProtoRetryError < ProtocolError ; end
+
+ class ReplyCode
+
+ class << self
+
+ def error_type( err )
+ @err = err
+ end
+
+ def error!( mes )
+ raise @err, mes
+ end
+
+ end
+
+ 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 = <<MES
+
+status %s
+writing string is:
+%s
+
+error message from server is:
+%s
+MES
+ type.error! sprintf( mes, @code, Net.quote(sending), Net.quote(@msg) )
+ end
+
+ end
+
+ class SuccessCode < ReplyCode
+ error_type ProtoUnknownError
+ end
+
+ class ContinueCode < SuccessCode
+ error_type ProtoUnknownError
+ end
+
+ class ErrorCode < ReplyCode
+ error_type ProtocolError
+ end
+
+ class SyntaxErrorCode < ErrorCode
+ error_type ProtoSyntaxError
+ end
+
+ class FatalErrorCode < ErrorCode
+ error_type ProtoFatalError
+ end
+
+ class ServerBusyCode < ErrorCode
+ error_type ProtoServerError
+ end
+
+ class RetryCode < ReplyCode
+ error_type ProtoRetryError
+ end
+
+ class UnknownCode < ReplyCode
+ error_type ProtoUnknownError
+ end
+
+
+
+ class WriteAdapter
+
+ def initialize( sock, mid )
+ @sock = sock
+ @mid = mid
+ end
+
+ def write( str )
+ @sock.__send__ @mid, str
+ end
+ alias << write
+
+ end
+
+ class ReadAdapter
+
+ def initialize( block )
+ @block = block
+ end
+
+ def <<( str )
+ @block.call str
+ end
+
+ end
+
+
+ class Socket
+
+ def initialize( addr, port, pipe = nil )
+ @addr = addr
+ @port = port
+ @pipe = pipe
+
+ @closed = true
+ @ipaddr = ''
+ @sending = ''
+ @buffer = ''
+
+ @socket = TCPsocket.new( addr, port )
+ @closed = false
+ @ipaddr = @socket.addr[3]
+ end
+
+ attr :pipe, true
+
+ class << self
+ alias open new
+ end
+
+ def reopen
+ unless closed? then
+ @socket.close
+ @buffer = ''
+ end
+ @socket = TCPsocket.new( @addr, @port )
+ end
+
+ attr :socket, true
+
+ def close
+ @socket.close
+ @closed = true
+ end
+
+ def closed?
+ @closed
+ end
+
+ def address
+ @addr.dup
+ end
+ alias addr address
+
+ attr_reader :port
+
+ def ip_address
+ @ipaddr.dup
+ end
+ alias ipaddr ip_address
+
+ attr_reader :sending
+
+
+ CRLF = "\r\n"
+ D_CRLF = ".\r\n"
+ TERMEXP = /\n|\r\n|\r/o
+
+
+ def read( len, ret = '' )
+ @pipe << "reading #{len} bytes...\n" if pre = @pipe ; @pipe = nil
+
+ rsize = 0
+ while rsize + @buffer.size < len do
+ rsize += writeinto( ret, @buffer.size )
+ fill_rbuf
+ end
+ writeinto( ret, len - rsize )
+
+ @pipe << "read #{len} bytes\n" if @pipe = pre
+ ret
+ end
+
+
+ def read_all( ret = '' )
+ @pipe << "reading all...\n" if pre = @pipe; @pipe = nil
+
+ rsize = 0
+ begin
+ while true do
+ rsize += writeinto( ret, @buffer.size )
+ fill_rbuf
+ end
+ rescue EOFError
+ ;
+ end
+
+ @pipe << "read #{rsize} bytes\n" if @pipe = pre
+ ret
+ end
+
+
+ def readuntil( target )
+ until idx = @buffer.index( target ) do
+ fill_rbuf
+ end
+
+ ret = ''
+ writeinto( ret, idx + target.size )
+ ret
+ end
+
+
+ def readline
+ ret = readuntil( CRLF )
+ ret.chop!
+ ret
+ end
+
+
+ def read_pendstr( dest = '' )
+ @pipe << "reading text...\n" if pre = @pipe ; @pipe = nil
+
+ rsize = 0
+
+ while (str = readuntil( CRLF )) != D_CRLF do
+ rsize += str.size
+ str.gsub!( /\A\./o, '' )
+ dest << str
+ end
+
+ @pipe << "read #{rsize} bytes\n" if @pipe = pre
+ dest
+ end
+
+
+ def read_pendlist
+ @pipe << "reading list...\n" if pre = @pipe ; @pipe = nil
+
+ arr = []
+ str = nil
+ call = iterator?
+
+ while (str = readuntil( CRLF )) != D_CRLF do
+ str.chop!
+ arr.push str
+ yield str if iterator?
+ end
+
+ @pipe << "read #{arr.size} lines\n" if @pipe = pre
+ arr
+ end
+
+
+ private
+
+
+ READ_BLOCK = 1024 * 8
+
+ def fill_rbuf
+ @buffer << @socket.sysread( READ_BLOCK )
+ end
+
+ def writeinto( ret, len )
+ bsi = @buffer.size
+ ret << @buffer[ 0, len ]
+ @buffer = @buffer[ len, bsi - len ]
+
+ @pipe << %{read "#{Net.quote ret}"\n} if @pipe
+ len
+ end
+
+
+ public
+
+
+ def write( str )
+ do_write_beg
+ do_write_do str
+ do_write_fin
+ end
+
+
+ def writeline( str )
+ do_write_beg
+ do_write_do str
+ do_write_do CRLF
+ do_write_fin
+ end
+
+
+ def write_bin( src, block = nil )
+ do_write_beg
+ if block then
+ block.call WriteAdapter.new( self, :do_write_do )
+ else
+ src.each do |bin|
+ do_write_do bin
+ end
+ end
+ do_write_fin
+ end
+
+
+ def write_pendstr( src )
+ @pipe << "writing text from #{src.type}\n" if pre = @pipe ; @pipe = nil
+
+ do_write_beg
+ if iterator? then
+ yield WriteAdapter.new( self, :write_pendstr_inner )
+ else
+ write_pendstr_inner src
+ end
+ each_crlf_line2( :i_w_pend )
+ do_write_do D_CRLF
+ wsize = do_write_fin
+
+ @pipe << "wrote #{wsize} bytes text" if @pipe = pre
+ wsize
+ end
+
+
+ private
+
+
+ def write_inner( src )
+ each_crlf_line( src, :do_write_do )
+ end
+
+
+ def write_pendstr_inner( src )
+ each_crlf_line src, :i_w_pend
+ end
+
+ def i_w_pend( line )
+ do_write_do '.' if line[0] == ?.
+ do_write_do line
+ end
+
+
+ def each_crlf_line( src, mid )
+ beg = 0
+ buf = pos = s = bin = nil
+
+ adding( src ) do
+ beg = 0
+ buf = @wbuf
+ while pos = buf.index( TERMEXP, beg ) do
+ s = $&.size
+ break if pos + s == buf.size - 1 and buf[-1] == ?\r
+
+ send mid, buf[ beg, pos - beg ] << CRLF
+ beg = pos + s
+ end
+ @wbuf = buf[ beg, buf.size - beg ] if beg != 0
+ end
+ end
+
+ def adding( src )
+ i = nil
+
+ case src
+ when String
+ 0.step( src.size, 512 ) do |i|
+ @wbuf << src[ i, 512 ]
+ yield
+ end
+
+ when File
+ while i = src.read( 512 ) do
+ @wbuf << i
+ yield
+ end
+
+ else
+ src.each do |bin|
+ @wbuf << bin
+ yield if @wbuf.size > 512
+ end
+ end
+ end
+
+ def each_crlf_line2( mid )
+ buf = @wbuf
+ beg = pos = nil
+
+ buf << "\n" unless /\n|\r/o === buf[-1,1]
+
+ beg = 0
+ while pos = buf.index( TERMEXP, beg ) do
+ send mid, buf[ beg, pos - beg ] << CRLF
+ beg = pos + $&.size
+ end
+ end
+
+
+ def do_write_beg
+ @writtensize = 0
+ @sending = ''
+ @wbuf = ''
+ end
+
+ def do_write_do( arg )
+ if @pipe or @sending.size < 128 then
+ @sending << Net.quote( arg )
+ else
+ @sending << '...' unless @sending[-1] == ?.
+ end
+
+ s = @socket.write( arg )
+ @writtensize += s
+ s
+ end
+
+ def do_write_fin
+ if @pipe then
+ @pipe << 'write "'
+ @pipe << @sending
+ @pipe << "\"\n"
+ end
+
+ @socket.flush
+ @writtensize
+ end
+
+ end
+
+
+ def Net.quote( str )
+ str = str.gsub( "\n", '\\n' )
+ str.gsub!( "\r", '\\r' )
+ str.gsub!( "\t", '\\t' )
+ str
+ end
+
+end # module Net
diff --git a/lib/net/session.rb b/lib/net/session.rb
index b0977e7e14..7cf423ce3a 100644
--- a/lib/net/session.rb
+++ b/lib/net/session.rb
@@ -1,82 +1,197 @@
-#
-# session.rb version 1.0.1
-#
-# author: Minero Aoki <aamine@dp.u-netsurf.ne.jp>
-#
+=begin
-require 'socket'
+= net/session.rb
+written by Minero Aoki <aamine@dp.u-netsurf.ne.jp>
-class String
+This library is distributed under the terms of the Ruby license.
+You can freely distribute/modify this library.
- def doquote
- str = self.gsub( "\n", '\\n' )
- str.gsub!( "\r", '\\r' )
- str.gsub!( "\t", '\\t' )
- return str
- end
+=end
-end
+require 'socket'
module Net
- DEBUG = $DEBUG
- # DEBUG = false
+ Version = '1.1.4'
+=begin
- class Session
+== Net::Protocol
- Version = '1.0.1'
+the abstruct class for Internet protocol
- def initialize( addr = 'localhost', port = nil )
- proto_initialize
- @address = addr
- @port = port if port
- @active = false
- end
+=== Super Class
+
+Object
+
+=== Constants
+
+: Version
+ The version of Session class. It is a string like "1.1.4".
+
+
+=== Class Methods
+
+: new( address = 'localhost', port = nil )
+ This method Creates a new Session object.
+
+: start( address = 'localhost', port = nil, *args )
+: start( address = 'localhost', port = nil, *args ){|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
+
+: address
+ the address of connecting server (FQDN).
+
+: port
+ connecting port number
+
+: start( *args )
+ 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 ends protocol. If you call this method before protocol starts,
+ it only return false without doing anything.
+
+: active?
+ true if session have been started
+
+=end
+
+ class Protocol
class << self
+
def start( address = 'localhost', port = nil, *args )
- inst = new( address, port )
- ret = inst.start( *args )
+ instance = new( address, port )
if iterator? then
- ret = yield( inst )
- inst.finish
+ instance.start( *args ) { yield instance }
+ else
+ instance.start *args
+ instance
end
- return ret
end
+
+ def Proxy( p_addr, p_port )
+ klass = Class.new( self )
+ klass.module_eval %-
+
+ def initialize( addr, port )
+ @proxyaddr = '#{p_addr}'
+ @proxyport = '#{p_port}'
+ super @proxyaddr, @proxyport
+ @address = addr
+ @port = port
+ end
+
+ def connect( addr, port )
+ super @proxyaddr, @proxyport
+ end
+ private :connect
+
+ attr :proxyaddr
+ attr :proxyport
+ -
+ def klass.proxy?
+ true
+ end
+
+ klass
+ end
+
+ def proxy?
+ false
+ end
+
+
+ private
+
+ def protocol_param( name, val )
+ module_eval %-
+ def self.#{name.id2name}
+ #{val}
+ end
+ -
+ end
+
+ end
+
+
+ #
+ # sub-class requirements
+ #
+ # protocol_param command_type
+ # protocol_param port
+ #
+ # private method do_start (optional)
+ # private method do_finish (optional)
+ #
+
+ protocol_param :port, 'nil'
+ protocol_param :command_type, 'nil'
+ protocol_param :socket_type, '::Net::ProtocolSocket'
+
+
+ def initialize( addr = nil, port = nil )
+ @address = addr || 'localhost'
+ @port = port || self.type.port
+
+ @active = false
+ @pipe = nil
+
+ @command = nil
+ @socket = nil
end
attr :address
attr :port
+ attr :command
attr :socket
- attr :proto_type
- attr :proto, true
def start( *args )
return false if active?
+ @active = true
- if ProtocolSocket === args[0] then
- @socket = args.shift
- else
- @socket = ProtocolSocket.open( @address, @port )
+ begin
+ connect @address, @port
+ do_start *args
+ yield if iterator?
+ ensure
+ finish if iterator?
end
- @proto = @proto_type.new( @socket )
- do_start( *args )
-
- @active = true
end
def finish
- if @proto then
+ if @command then
do_finish
- @proto = nil
+ disconnect
+ end
+
+ if @socket and not @socket.closed? then
+ @socket.close
+ @socket = nil
+ end
+
+ if active? then
+ @active = false
return true
else
@@ -84,20 +199,72 @@ module Net
end
end
- def active?() @active end
+ def active?
+ @active
+ end
+
+ def set_pipe( arg ) # un-documented
+ @pipe = arg
+ end
+
+
+ private
+
+
+ def do_start
+ end
+
+ def do_finish
+ end
+
+
+ def connect( addr, port )
+ @socket = self.type.socket_type.open( addr, port, @pipe )
+ @command = self.type.command_type.new( @socket )
+ end
+
+ def disconnect
+ @command.quit
+ @command = nil
+ @socket = nil
+ end
end
+ 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
def initialize( sock )
@socket = sock
- check_reply( SuccessCode )
+ @error_occured = false
end
attr :socket, true
+ attr :error_occured
def quit
if @socket and not @socket.closed? then
@@ -107,21 +274,31 @@ module Net
@socket.close unless @socket.closed?
@socket = nil
end
+ @error_occured = false
end
end
private
def check_reply( *oks )
- rep = get_reply
+ reply_must( get_reply, *oks )
+ end
+
+ def reply_must( rep, *oks )
oks.each do |i|
if i === rep then
return rep
end
end
+ @error_occured = true
rep.error! @socket.sending
end
+
+ def getok( line, ok = SuccessCode )
+ @socket.writeline line
+ check_reply ok
+ end
end
@@ -133,6 +310,7 @@ module Net
class ProtoServerError < ProtocolError ; end
class ProtoAuthError < ProtocolError ; end
class ProtoCommandError < ProtocolError ; end
+ class ProtoRetryError < ProtocolError ; end
class ReplyCode
@@ -145,64 +323,157 @@ module Net
attr :msg
def error!( sending )
- err, tag = Errors[ self.type ]
- mes = sprintf( <<MES, tag, @code, sending.doquote, @msg.doquote )
+ mes = <<MES
-%s: status %s
+status %s
writing string is:
%s
error message from server is:
%s
MES
- raise err, mes
+ raise self.type::Error,
+ sprintf( mes, @code, Net.quote(sending), Net.quote(@msg) )
end
end
- class SuccessCode < ReplyCode ; end
- class ContinueCode < SuccessCode ; end
- class ErrorCode < ReplyCode ; end
- class SyntaxErrorCode < ErrorCode ; end
- class FatalErrorCode < ErrorCode ; end
- class ServerBusyCode < ErrorCode ; end
- class UnknownCode < ReplyCode ; end
+ class SuccessCode < ReplyCode
+ Error = ProtoUnknownError
+ end
- class ReplyCode
- Errors = {
- SuccessCode => [ ProtoUnknownError, 'unknown error' ],
- ContinueCode => [ ProtoUnknownError, 'unknown error' ],
- ErrorCode => [ ProtocolError, 'protocol error' ],
- SyntaxErrorCode => [ ProtoSyntaxError, 'syntax error' ],
- FatalErrorCode => [ ProtoFatalError, 'fatal error' ],
- ServerBusyCode => [ ProtoServerError, 'probably server busy' ],
- UnknownCode => [ ProtoUnknownError, 'unknown error' ]
- }
+ class ContinueCode < SuccessCode
+ Error = ProtoUnknownError
+ end
+
+ class ErrorCode < ReplyCode
+ Error = ProtocolError
+ end
+
+ class SyntaxErrorCode < ErrorCode
+ Error = ProtoSyntaxError
+ end
+
+ class FatalErrorCode < ErrorCode
+ Error = ProtoFatalError
+ end
+
+ class ServerBusyCode < ErrorCode
+ Error = ProtoServerError
end
+ class RetryCode < ReplyCode
+ Error = ProtoRetryError
+ end
+
+ class UnknownCode < ReplyCode
+ Error = ProtoUnknownError
+ end
+
+
+=begin
+
+== Net::ProtocolSocket
+
+=== Super Class
+
+Object
+
+=== Class Methods
+
+: new( address = 'localhost', port = nil )
+ This create new ProtocolSocket object, and connect to server.
+
+
+=== Methods
+
+: close
+ This method closes socket.
+
+: address, addr
+ a FQDN address of server
+
+: ip_address, ipaddr
+ an IP address of server
+
+: port
+ connecting port number.
+
+: closed?
+ true if ProtocolSokcet have been closed already
+
+
+: read( length )
+ This method read 'length' bytes and return the string.
+
+: readuntil( target )
+ This method read until find 'target'. Returns read string.
+
+: readline
+ read until "\r\n" and returns it without "\r\n".
+
+: read_pendstr
+ This method read until "\r\n.\r\n".
+ At the same time, delete period at line head and final line ("\r\n.\r\n").
+
+: read_pendlist
+: read_pendlist{|line| .... }
+ This method read until "\r\n.\r\n". This method resembles to 'read_pendstr',
+ but 'read_pendlist' don't check period at line head, and returns array which
+ each element is one line.
+
+ When this method was called with block, evaluate it for each reading a line.
+
+: write( src )
+ This method send 'src'. ProtocolSocket read strings from 'src' by 'each'
+ iterator. This method returns written bytes.
+
+: writebin( src )
+ This method send 'src'. ProtocolSokcet read string from 'src' by 'each'
+ iterator. This method returns written bytes.
+
+: writeline( str )
+ This method writes 'str'. There has not to be bare "\r" or "\n" in 'str'.
+
+: write_pendstr( src )
+ This method writes 'src' as a mail.
+ ProtocolSocket reads strings from 'src' by 'each' iterator.
+ This returns written bytes.
+
+=end
class ProtocolSocket
- def initialize( addr, port )
- @address = addr
- @port = port
+ def initialize( addr, port, pipe = nil )
+ @addr = addr
+ @port = port
+ @pipe = pipe
+ @closed = true
@ipaddr = ''
- @closed = false
@sending = ''
@buffer = ''
@socket = TCPsocket.new( addr, port )
+ @closed = false
@ipaddr = @socket.addr[3]
-
- @dout = Net::DEBUG
end
+ attr :pipe, true
+
class << self
alias open new
end
+ def reopen
+ unless closed? then
+ @socket.close
+ @buffer = ''
+ end
+ @socket = TCPsocket.new( @addr, @port )
+ end
+
attr :socket, true
@@ -211,11 +482,21 @@ MES
@closed = true
end
- def closed?() @closed end
+ def closed?
+ @closed
+ end
+
+ def address
+ @addr.dup
+ end
+ alias addr address
- def addr() @address.dup end
- def port() @port end
- def ipaddr() @ipaddr.dup end
+ attr :port
+
+ def ip_address
+ @ipaddr.dup
+ end
+ alias ipaddr ip_address
attr :sending
@@ -226,16 +507,35 @@ MES
def read( len, ret = '' )
- rsize = 0
+ @pipe << "reading #{len} bytes...\n" if pre = @pipe ; @pipe = nil
+ rsize = 0
while rsize + @buffer.size < len do
- rsize += @buffer.size
- ret << fetch_rbuf( @buffer.size )
+ rsize += writeinto( ret, @buffer.size )
fill_rbuf
end
- ret << fetch_rbuf( len - rsize )
+ writeinto( ret, len - rsize )
- return ret
+ @pipe << "read #{len} bytes\n" if @pipe = pre
+ ret
+ end
+
+
+ def read_all( ret = '' )
+ @pipe << "reading all...\n" if pre = @pipe; @pipe = nil
+
+ rsize = 0
+ begin
+ while true do
+ rsize += writeinto( ret, @buffer.size )
+ fill_rbuf
+ end
+ rescue EOFError
+ ;
+ end
+
+ @pipe << "read #{rsize} bytes\n" if @pipe = pre
+ ret
end
@@ -244,19 +544,21 @@ MES
fill_rbuf
end
- return fetch_rbuf( idx + target.size )
+ ret = ''
+ writeinto( ret, idx + target.size )
+ ret
end
def readline
ret = readuntil( CRLF )
ret.chop!
- return ret
+ ret
end
def read_pendstr( dest = '' )
- $stderr.puts "reading pendstr" if pre = @dout ; @dout = false
+ @pipe << "reading text...\n" if pre = @pipe ; @pipe = nil
rsize = 0
@@ -266,12 +568,14 @@ MES
dest << str
end
- $stderr.puts "read pendstr #{rsize} bytes" if @dout = pre
- return dest
+ @pipe << "read #{rsize} bytes\n" if @pipe = pre
+ dest
end
def read_pendlist
+ @pipe << "reading list...\n" if pre = @pipe ; @pipe = nil
+
arr = []
str = nil
call = iterator?
@@ -282,7 +586,8 @@ MES
yield str if iterator?
end
- return arr
+ @pipe << "read #{arr.size} lines\n" if @pipe = pre
+ arr
end
@@ -295,22 +600,16 @@ MES
@buffer << @socket.sysread( READ_BLOCK )
end
- def fetch_rbuf( len )
+ def writeinto( ret, len )
bsi = @buffer.size
- ret = @buffer[ 0, len ]
+ ret << @buffer[ 0, len ]
@buffer = @buffer[ len, bsi - len ]
- if @dout then
- $stderr.print 'read "'
- debugout ret
- $stderr.print "\"\n"
- end
- return ret
+ @pipe << %{read "#{Net.quote ret}"\n} if @pipe
+ len
end
- ### write
-
public
@@ -319,7 +618,7 @@ MES
each_crlf_line( src ) do |line|
do_write_do line
end
- return do_write_fin
+ do_write_fin
end
@@ -328,7 +627,7 @@ MES
src.each do |bin|
do_write_do bin
end
- return do_write_fin
+ do_write_fin
end
@@ -336,13 +635,12 @@ MES
do_write_beg
do_write_do str
do_write_do CRLF
- return do_write_fin
+ do_write_fin
end
def write_pendstr( src )
- $stderr.puts "writing pendstr from #{src.type}" if pre = @dout
- @dout = false
+ @pipe << "writing text from #{src.type}" if pre = @pipe ; @pipe = nil
do_write_beg
each_crlf_line( src ) do |line|
@@ -352,8 +650,8 @@ MES
do_write_do D_CRLF
wsize = do_write_fin
- $stderr.puts "wrote pendstr #{wsize} bytes" if @dout = pre
- return wsize
+ @pipe << "wrote #{wsize} bytes text" if @pipe = pre
+ wsize
end
@@ -363,18 +661,18 @@ MES
def each_crlf_line( src )
buf = ''
beg = 0
- pos = nil
+ pos = s = bin = nil
- src.each do |b|
- buf << b
+ src.each do |bin|
+ buf << bin
beg = 0
- while (pos = buf.index(TERMEXP, beg)) and (pos < buf.size - 2) do
- pos += $&.size
- tmp = buf[ beg, pos - beg ]
- tmp.chop!
- yield tmp << CRLF
- beg = pos
+ while pos = buf.index( TERMEXP, beg ) do
+ s = $&.size
+ break if pos + s == buf.size - 1 and buf[-1] == ?\r
+
+ yield buf[ beg, pos - beg ] << CRLF
+ beg = pos + s
end
buf = buf[ beg, buf.size - beg ] if beg != 0
end
@@ -382,52 +680,49 @@ MES
buf << "\n" unless /\n|\r/o === buf[-1,1]
beg = 0
- while pos = buf.index(TERMEXP, beg) do
- pos += $&.size
- tmp = buf[ beg, pos - beg ]
- tmp.chop!
- yield tmp << CRLF
- beg = pos
+ while pos = buf.index( TERMEXP, beg ) do
+ yield buf[ beg, pos - beg ] << CRLF
+ beg = pos + $&.size
end
end
def do_write_beg
- $stderr.print 'write "' if @dout
-
@writtensize = 0
@sending = ''
end
def do_write_do( arg )
- debugout arg if @dout
-
- if @sending.size < 128 then
- @sending << arg
+ if @pipe or @sending.size < 128 then
+ @sending << Net.quote( arg )
else
@sending << '...' unless @sending[-1] == ?.
end
+
s = @socket.write( arg )
@writtensize += s
- return s
+ s
end
def do_write_fin
- $stderr.puts if @dout
+ if @pipe then
+ @pipe << 'write "'
+ @pipe << @sending
+ @pipe << "\"\n"
+ end
@socket.flush
- return @writtensize
+ @writtensize
end
+ end
- def debugout( ret )
- while ret and tmp = ret[ 0, 50 ] do
- ret = ret[ 50, ret.size - 50 ]
- tmp = tmp.inspect
- $stderr.print tmp[ 1, tmp.size - 2 ]
- end
- end
+ def Net.quote( str )
+ str = str.gsub( "\n", '\\n' )
+ str.gsub!( "\r", '\\r' )
+ str.gsub!( "\t", '\\t' )
+ str
end
-end
+end # module Net
diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb
index 9f534c20c0..7a04aa2aa2 100644
--- a/lib/net/smtp.rb
+++ b/lib/net/smtp.rb
@@ -1,89 +1,166 @@
-#
-# smtp.rb version 1.0.1
-#
-# author Minero Aoki <aamine@dp.u-netsurf.ne.jp>
-#
+=begin
+
+= net/smtp.rb
+
+written by Minero Aoki <aamine@dp.u-netsurf.ne.jp>
+
+This library is distributed under the terms of the Ruby license.
+You can freely distribute/modify this library.
+
+=end
+
require 'net/session'
module Net
- class SMTPSession < Session
- def proto_initialize
- @proto_type = SMTPCommand
- @port = 25
- end
+=begin
+
+== Net::SMTP
+
+=== Super Class
+
+Net::Protocol
+
+=== Class Methods
+
+: new( address = 'localhost', port = 25 )
+ This method create new SMTP object.
+
+
+=== Methods
+
+: start( helo_domain = ENV['HOSTNAME'] )
+ 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.
+
+ 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 ends SMTP.
+ If protocol had not started, do nothind and return false.
+
+=end
+
+ class SMTP < Protocol
+
+ 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
private
- def do_start( helodom = nil )
+ def do_start( helodom = ENV['HOSTNAME'] )
unless helodom then
- helodom = ENV[ 'HOSTNAME' ]
+ raise ArgumentError, "cannot get hostname"
end
- @proto.helo( helodom )
- end
-
- def do_finish
- @proto.quit
+ @command.helo helodom
end
end
- SMTP = SMTPSession
+ SMTPSession = SMTP
+
+
+=begin
+
+== Net::SMTPCommand
+
+=== Super Class
+
+Net::Command
+
+=== Class Methods
+
+: new( socket )
+ This method creates new SMTPCommand object, and open SMTP.
+
+
+=== Methods
+
+: helo( helo_domain )
+ This method send "HELO" command and start SMTP.
+ helo_domain is localhost's FQDN.
+: mailfrom( from_addr )
+ This method sends "MAIL FROM" command.
+ from_addr is your mail address(????@????).
+: rcpt( to_addrs )
+ This method sends "RCPT TO" command.
+ to_addrs is array of mail address(???@???) of destination.
+
+: data( mailsrc )
+ This method send 'mailsrc' as mail. SMTP reads strings from 'mailsrc'
+ by calling 'each' iterator.
+
+: quit
+ This method sends "QUIT" command and ends SMTP session.
+
+=end
class SMTPCommand < Command
+ def initialize( sock )
+ super
+ check_reply SuccessCode
+ end
+
+
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 sendmail( mailsrc )
- @socket.write_pendstr( mailsrc )
- check_reply( SuccessCode )
+ def writemail( mailsrc )
+ @socket.write_pendstr mailsrc
+ check_reply SuccessCode
end
+ alias sendmail writemail
private
def do_quit
- @socket.writeline( 'QUIT' )
- check_reply( SuccessCode )
+ getok 'QUIT'
end
@@ -91,19 +168,19 @@ module Net
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
-
- return cls.new( stat, arr.join('') )
+ 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
+
+ klass.new( stat, arr.join('') )
end
@@ -120,9 +197,4 @@ module Net
end
-
- unless Session::Version == '1.0.1' then
- $stderr.puts "WARNING: wrong version of session.rb & smtp.rb"
- end
-
end
diff --git a/lib/net/telnet.rb b/lib/net/telnet.rb
new file mode 100644
index 0000000000..4acaaadd3a
--- /dev/null
+++ b/lib/net/telnet.rb
@@ -0,0 +1,749 @@
+=begin
+$Date$
+
+== SIMPLE TELNET CLIENT LIBRARY
+
+net/telnet.rb
+
+Version 1.30
+
+Wakou Aoyama <wakou@fsinet.or.jp>
+
+
+=== MAKE NEW TELNET OBJECT
+
+ host = Net::Telnet::new({
+ "Binmode" => false, # default: false
+ "Host" => "localhost", # default: "localhost"
+ "Output_log" => "output_log", # default: nil (no output)
+ "Dump_log" => "dump_log", # default: nil (no output)
+ "Port" => 23, # default: 23
+ "Prompt" => /[$%#>] \z/n, # default: /[$%#>] \z/n
+ "Telnetmode" => true, # default: true
+ "Timeout" => 10, # default: 10
+ # if ignore timeout then set "Timeout" to false.
+ "Waittime" => 0, # default: 0
+ "Proxy" => proxy # default: nil
+ # proxy is Telnet or TCPsocket object
+ })
+
+Telnet object has socket class methods.
+
+if set "Telnetmode" option to false. not telnet command interpretation.
+"Waittime" is time to confirm "Prompt". There is a possibility that
+the same character as "Prompt" is included in the data, and, when
+the network or the host is very heavy, the value is enlarged.
+
+=== STATUS OUTPUT
+
+ host = Net::Telnet::new({"Host" => "localhost"}){|c| print c }
+
+connection status output.
+
+example
+
+ Trying localhost...
+ Connected to localhost.
+
+
+=== WAIT FOR MATCH
+
+ line = host.waitfor(/match/)
+ line = host.waitfor({"Match" => /match/,
+ "String" => "string",
+ "Timeout" => secs})
+ # if ignore timeout then set "Timeout" to false.
+
+if set "String" option, then Match == Regexp.new(quote("string"))
+
+
+==== REALTIME OUTPUT
+
+ host.waitfor(/match/){|c| print c }
+ host.waitfor({"Match" => /match/,
+ "String" => "string",
+ "Timeout" => secs}){|c| print c}
+
+of cource, set sync=true or flush is necessary.
+
+
+=== SEND STRING AND WAIT PROMPT
+
+ line = host.cmd("string")
+ line = host.cmd({"String" => "string",
+ "Prompt" => /[$%#>] \z/n,
+ "Timeout" => 10})
+
+
+==== REALTIME OUTPUT
+
+ host.cmd("string"){|c| print c }
+ host.cmd({"String" => "string",
+ "Prompt" => /[$%#>] \z/n,
+ "Timeout" => 10}){|c| print c }
+
+of cource, set sync=true or flush is necessary.
+
+
+=== SEND STRING
+
+ host.print("string")
+ # == host.write("string\n")
+
+
+=== TURN TELNET COMMAND INTERPRETATION
+
+ host.telnetmode # turn on/off
+ host.telnetmode(true) # on
+ host.telnetmode(false) # off
+
+
+=== TOGGLE NEWLINE TRANSLATION
+
+ host.binmode # turn true/false
+ host.binmode(true) # no translate newline
+ host.binmode(false) # translate newline
+
+
+=== LOGIN
+
+ host.login("username", "password")
+ host.login({"Name" => "username",
+ "Password" => "password",
+ "Prompt" => /[$%#>] \z/n,
+ "Timeout" => 10})
+
+if no password prompt.
+
+ host.login("username")
+ host.login({"Name" => "username",
+ "Prompt" => /[$%#>] \z/n,
+ "Timeout" => 10})
+
+
+==== REALTIME OUTPUT
+
+ host.login("username", "password"){|c| print c }
+ host.login({"Name" => "username",
+ "Password" => "password",
+ "Prompt" => /[$%#>] \z/n,
+ "Timeout" => 10}){|c| print c }
+
+of cource, set sync=true or flush is necessary.
+
+
+== EXAMPLE
+
+=== LOGIN AND SEND COMMAND
+
+ localhost = Net::Telnet::new({"Host" => "localhost",
+ "Timeout" => 10,
+ "Prompt" => /[$%#>] \z/n})
+ localhost.login("username", "password"){|c| print c }
+ localhost.cmd("command"){|c| print c }
+ localhost.close
+
+
+=== CHECKS A POP SERVER TO SEE IF YOU HAVE MAIL
+
+ pop = Net::Telnet::new({"Host" => "your_destination_host_here",
+ "Port" => 110,
+ "Telnetmode" => false,
+ "Prompt" => /^\+OK/n})
+ pop.cmd("user " + "your_username_here"){|c| print c}
+ pop.cmd("pass " + "your_password_here"){|c| print c}
+ pop.cmd("list"){|c| print c}
+
+
+== HISTORY
+
+=== Version 1.30
+
+2000/04/03 18:27:02
+
+- telnet.rb --> net/telnet.rb
+
+=== Version 1.20
+
+2000/01/24 17:02:57
+
+- 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>
+
+=== Version 1.10
+
+2000/01/18 17:47:31
+
+- bug fix: write method
+- respond to "IAC WILL BINARY" with "IAC DO BINARY"
+
+=== Version 1.00
+
+1999/10/04 22:51:26
+
+- 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
+
+=== Version 0.50
+
+1999/09/21 21:24:07
+
+- add write method
+
+=== Version 0.40
+
+1999/09/17 17:41:41
+
+- bug fix: preprocess method
+
+=== Version 0.30
+
+1999/09/14 23:09:05
+
+- change prompt check order.
+ not IO::select([@sock], nil, nil, waittime) and prompt === line
+ --> prompt === line and not IO::select([@sock], nil, nil, waittime)
+
+=== Version 0.24
+
+1999/09/13 22:28:33
+
+- Telnet#login
+if ommit password, then not require password prompt.
+
+=== Version 0.232
+
+1999/08/10 05:20:21
+
+- 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 }
+
+=== Version 0.231
+
+1999/07/16 13:39:42
+
+- TRUE --> true, FALSE --> false
+
+=== Version 0.23
+
+1999/07/15 22:32:09
+
+- waitfor: if end of file reached, then return nil.
+
+=== Version 0.22
+
+1999/06/29 09:08:51
+
+- new, waitfor, cmd: {"Timeout" => false} # ignore timeout
+
+=== Version 0.21
+
+1999/06/28 18:18:55
+
+- waitfor: not rescue (EOFError)
+
+=== Version 0.20
+
+1999/06/04 06:24:58
+
+- waitfor: support for divided telnet command
+
+=== Version 0.181
+
+1999/05/22
+
+- bug fix: print method
+
+=== Version 0.18
+
+1999/05/14
+
+- respond to "IAC WON'T SGA" with "IAC DON'T SGA"
+- DON'T SGA : end of line --> CR + LF
+- bug fix: preprocess method
+
+=== Version 0.17
+
+1999/04/30
+
+- bug fix: $! + "\n" --> $!.to_s + "\n"
+
+=== Version 0.163
+
+1999/04/11
+
+- STDOUT.write(message) --> yield(message) if iterator?
+
+=== Version 0.162
+
+1999/03/17
+
+- add "Proxy" option
+- required timeout.rb
+
+=== Version 0.161
+
+1999/02/03
+
+- select --> IO::select
+
+=== Version 0.16
+
+1998/10/09
+
+- preprocess method change for the better
+- add binmode method.
+- change default Binmode. TRUE --> FALSE
+
+=== Version 0.15
+
+1998/10/04
+
+- add telnetmode method.
+
+=== Version 0.141
+
+1998/09/22
+
+- change default prompt. /[$%#>] $/ --> /[$%#>] \Z/
+
+=== Version 0.14
+
+1998/09/01
+
+- IAC WILL SGA send EOL --> CR+NULL
+- IAC WILL SGA IAC DO BIN send EOL --> CR
+- NONE send EOL --> LF
+- add Dump_log option.
+
+=== Version 0.13
+
+1998/08/25
+
+- add print method.
+
+=== Version 0.122
+
+1998/08/05
+
+- support for HP-UX 10.20 thanks to WATANABE Tetsuya <tetsu@jpn.hp.com>
+- socket.<< --> socket.write
+
+=== Version 0.121
+
+1998/07/15
+
+- string.+= --> string.concat
+
+=== Version 0.12
+
+1998/06/01
+
+- add timeout, waittime.
+
+=== Version 0.11
+
+1998/04/21
+
+- add realtime output.
+
+=== Version 0.10
+
+1998/04/13
+
+- first release.
+
+=end
+
+require "socket"
+require "delegate"
+require "timeout"
+
+module Net
+ class Telnet < SimpleDelegator
+
+ IAC = 255.chr # "\377" # "\xff" # interpret as command:
+ DONT = 254.chr # "\376" # "\xfe" # you are not to use option
+ DO = 253.chr # "\375" # "\xfd" # please, you use option
+ WONT = 252.chr # "\374" # "\xfc" # I won't use option
+ WILL = 251.chr # "\373" # "\xfb" # I will use option
+ SB = 250.chr # "\372" # "\xfa" # interpret as subnegotiation
+ GA = 249.chr # "\371" # "\xf9" # you may reverse the line
+ EL = 248.chr # "\370" # "\xf8" # erase the current line
+ EC = 247.chr # "\367" # "\xf7" # erase the current character
+ AYT = 246.chr # "\366" # "\xf6" # are you there
+ AO = 245.chr # "\365" # "\xf5" # abort output--but let prog finish
+ IP = 244.chr # "\364" # "\xf4" # interrupt process--permanently
+ BREAK = 243.chr # "\363" # "\xf3" # break
+ DM = 242.chr # "\362" # "\xf2" # data mark--for connect. cleaning
+ NOP = 241.chr # "\361" # "\xf1" # nop
+ SE = 240.chr # "\360" # "\xf0" # end sub negotiation
+ EOR = 239.chr # "\357" # "\xef" # end of record (transparent mode)
+ ABORT = 238.chr # "\356" # "\xee" # Abort process
+ SUSP = 237.chr # "\355" # "\xed" # Suspend process
+ EOF = 236.chr # "\354" # "\xec" # End of file
+ SYNCH = 242.chr # "\362" # "\xf2" # for telfunc calls
+
+ OPT_BINARY = 0.chr # "\000" # "\x00" # Binary Transmission
+ OPT_ECHO = 1.chr # "\001" # "\x01" # Echo
+ OPT_RCP = 2.chr # "\002" # "\x02" # Reconnection
+ OPT_SGA = 3.chr # "\003" # "\x03" # Suppress Go Ahead
+ OPT_NAMS = 4.chr # "\004" # "\x04" # Approx Message Size Negotiation
+ OPT_STATUS = 5.chr # "\005" # "\x05" # Status
+ OPT_TM = 6.chr # "\006" # "\x06" # Timing Mark
+ OPT_RCTE = 7.chr # "\a" # "\x07" # Remote Controlled Trans and Echo
+ OPT_NAOL = 8.chr # "\010" # "\x08" # Output Line Width
+ OPT_NAOP = 9.chr # "\t" # "\x09" # Output Page Size
+ OPT_NAOCRD = 10.chr # "\n" # "\x0a" # Output Carriage-Return Disposition
+ OPT_NAOHTS = 11.chr # "\v" # "\x0b" # Output Horizontal Tab Stops
+ OPT_NAOHTD = 12.chr # "\f" # "\x0c" # Output Horizontal Tab Disposition
+ OPT_NAOFFD = 13.chr # "\r" # "\x0d" # Output Formfeed Disposition
+ OPT_NAOVTS = 14.chr # "\016" # "\x0e" # Output Vertical Tabstops
+ OPT_NAOVTD = 15.chr # "\017" # "\x0f" # Output Vertical Tab Disposition
+ OPT_NAOLFD = 16.chr # "\020" # "\x10" # Output Linefeed Disposition
+ OPT_XASCII = 17.chr # "\021" # "\x11" # Extended ASCII
+ OPT_LOGOUT = 18.chr # "\022" # "\x12" # Logout
+ OPT_BM = 19.chr # "\023" # "\x13" # Byte Macro
+ OPT_DET = 20.chr # "\024" # "\x14" # Data Entry Terminal
+ OPT_SUPDUP = 21.chr # "\025" # "\x15" # SUPDUP
+ OPT_SUPDUPOUTPUT = 22.chr # "\026" # "\x16" # SUPDUP Output
+ OPT_SNDLOC = 23.chr # "\027" # "\x17" # Send Location
+ OPT_TTYPE = 24.chr # "\030" # "\x18" # Terminal Type
+ OPT_EOR = 25.chr # "\031" # "\x19" # End of Record
+ OPT_TUID = 26.chr # "\032" # "\x1a" # TACACS User Identification
+ OPT_OUTMRK = 27.chr # "\e" # "\x1b" # Output Marking
+ OPT_TTYLOC = 28.chr # "\034" # "\x1c" # Terminal Location Number
+ OPT_3270REGIME = 29.chr # "\035" # "\x1d" # Telnet 3270 Regime
+ OPT_X3PAD = 30.chr # "\036" # "\x1e" # X.3 PAD
+ OPT_NAWS = 31.chr # "\037" # "\x1f" # Negotiate About Window Size
+ OPT_TSPEED = 32.chr # " " # "\x20" # Terminal Speed
+ OPT_LFLOW = 33.chr # "!" # "\x21" # Remote Flow Control
+ OPT_LINEMODE = 34.chr # "\"" # "\x22" # Linemode
+ OPT_XDISPLOC = 35.chr # "#" # "\x23" # X Display Location
+ OPT_OLD_ENVIRON = 36.chr # "$" # "\x24" # Environment Option
+ OPT_AUTHENTICATION = 37.chr # "%" # "\x25" # Authentication Option
+ OPT_ENCRYPT = 38.chr # "&" # "\x26" # Encryption Option
+ OPT_NEW_ENVIRON = 39.chr # "'" # "\x27" # New Environment Option
+ OPT_EXOPL = 255.chr # "\377" # "\xff" # Extended-Options-List
+
+ NULL = "\000"
+ CR = "\015"
+ LF = "\012"
+ EOL = CR + LF
+ v = $-v
+ $-v = false
+ VERSION = "1.30"
+ RELEASE_DATE = "$Date$"
+ $-v = v
+
+ def initialize(options)
+ @options = options
+ @options["Binmode"] = false unless @options.has_key?("Binmode")
+ @options["Host"] = "localhost" unless @options.has_key?("Host")
+ @options["Port"] = 23 unless @options.has_key?("Port")
+ @options["Prompt"] = /[$%#>] \z/n unless @options.has_key?("Prompt")
+ @options["Telnetmode"] = true unless @options.has_key?("Telnetmode")
+ @options["Timeout"] = 10 unless @options.has_key?("Timeout")
+ @options["Waittime"] = 0 unless @options.has_key?("Waittime")
+
+ @telnet_option = { "SGA" => false, "BINARY" => false }
+
+ if @options.has_key?("Output_log")
+ @log = File.open(@options["Output_log"], 'a+')
+ @log.sync = true
+ @log.binmode
+ end
+
+ if @options.has_key?("Dump_log")
+ @dumplog = File.open(@options["Dump_log"], 'a+')
+ @dumplog.sync = true
+ @dumplog.binmode
+ def @dumplog.log_dump(dir, x)
+ len = x.length
+ addr = 0
+ offset = 0
+ while 0 < len
+ if len < 16
+ line = x[offset, len]
+ else
+ line = x[offset, 16]
+ end
+ hexvals = line.unpack('H*')[0]
+ hexvals.concat ' ' * (32 - hexvals.length)
+ hexvals = format "%s %s %s %s " * 4, *hexvals.unpack('a2' * 16)
+ line.gsub! /[\000-\037\177-\377]/n, '.'
+ printf "%s 0x%5.5x: %s%s\n", dir, addr, hexvals, line
+ addr += 16
+ offset += 16
+ len -= 16
+ end
+ print "\n"
+ end
+ end
+
+ if @options.has_key?("Proxy")
+ if @options["Proxy"].kind_of?(Telnet)
+ @sock = @options["Proxy"].sock
+ elsif @options["Proxy"].kind_of?(TCPsocket)
+ @sock = @options["Proxy"]
+ else
+ raise "Error; Proxy is Telnet or TCPSocket object."
+ end
+ else
+ message = "Trying " + @options["Host"] + "...\n"
+ yield(message) if iterator?
+ @log.write(message) if @options.has_key?("Output_log")
+ @dumplog.log_dump('#', message) if @options.has_key?("Dump_log")
+
+ begin
+ if @options["Timeout"] == false
+ @sock = TCPsocket.open(@options["Host"], @options["Port"])
+ else
+ timeout(@options["Timeout"]){
+ @sock = TCPsocket.open(@options["Host"], @options["Port"])
+ }
+ end
+ rescue TimeoutError
+ raise TimeoutError, "timed-out; opening of the host"
+ rescue
+ @log.write($!.to_s + "\n") if @options.has_key?("Output_log")
+ @dumplog.log_dump('#', $!.to_s + "\n") if @options.has_key?("Dump_log")
+ raise
+ end
+ @sock.sync = true
+ @sock.binmode
+
+ message = "Connected to " + @options["Host"] + ".\n"
+ yield(message) if iterator?
+ @log.write(message) if @options.has_key?("Output_log")
+ @dumplog.log_dump('#', message) if @options.has_key?("Dump_log")
+ end
+
+ super(@sock)
+ end # initialize
+
+ attr :sock
+
+ def telnetmode(mode = 'turn')
+ if 'turn' == mode
+ @options["Telnetmode"] = @options["Telnetmode"] ? false : true
+ else
+ @options["Telnetmode"] = mode ? true : false
+ end
+ end
+
+ def binmode(mode = 'turn')
+ if 'turn' == mode
+ @options["Binmode"] = @options["Binmode"] ? false : true
+ else
+ @options["Binmode"] = mode ? true : false
+ end
+ end
+
+ def preprocess(string)
+ str = string.dup
+
+ # combine CR+NULL into CR
+ str.gsub!(/#{CR}#{NULL}/no, CR) if @options["Telnetmode"]
+
+ # combine EOL into "\n"
+ str.gsub!(/#{EOL}/no, "\n") unless @options["Binmode"]
+
+ str.gsub!(/#{IAC}(
+ [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|
+ [#{DO}#{DONT}#{WILL}#{WONT}]
+ [#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]|
+ #{SB}[^#{IAC}]*#{IAC}#{SE}
+ )/xno){
+ if IAC == $1 # handle escaped IAC characters
+ IAC
+ elsif AYT == $1 # respond to "IAC AYT" (are you there)
+ self.write("nobody here but us pigeons" + EOL)
+ ''
+ elsif DO[0] == $1[0] # respond to "IAC DO x"
+ if OPT_BINARY[0] == $1[1]
+ @telnet_option["BINARY"] = true
+ self.write(IAC + WILL + OPT_BINARY)
+ else
+ self.write(IAC + WONT + $1[1..1])
+ end
+ ''
+ elsif DONT[0] == $1[0] # respond to "IAC DON'T x" with "IAC WON'T x"
+ self.write(IAC + WONT + $1[1..1])
+ ''
+ elsif WILL[0] == $1[0] # respond to "IAC WILL x"
+ if OPT_BINARY[0] == $1[1]
+ self.write(IAC + DO + OPT_BINARY)
+ elsif OPT_ECHO[0] == $1[1]
+ self.write(IAC + DO + OPT_ECHO)
+ elsif OPT_SGA[0] == $1[1]
+ @telnet_option["SGA"] = true
+ self.write(IAC + DO + OPT_SGA)
+ else
+ self.write(IAC + DONT + $1[1..1])
+ end
+ ''
+ elsif WONT[0] == $1[0] # respond to "IAC WON'T x"
+ if OPT_ECHO[0] == $1[1]
+ self.write(IAC + DONT + OPT_ECHO)
+ elsif OPT_SGA[0] == $1[1]
+ @telnet_option["SGA"] = false
+ self.write(IAC + DONT + OPT_SGA)
+ else
+ self.write(IAC + DONT + $1[1..1])
+ end
+ ''
+ else
+ ''
+ end
+ }
+
+ str
+ end # preprocess
+
+ def waitfor(options)
+ time_out = @options["Timeout"]
+ waittime = @options["Waittime"]
+
+ if options.kind_of?(Hash)
+ prompt = if options.has_key?("Match")
+ options["Match"]
+ elsif options.has_key?("Prompt")
+ options["Prompt"]
+ elsif options.has_key?("String")
+ Regexp.new( Regexp.quote(options["String"]) )
+ end
+ time_out = options["Timeout"] if options.has_key?("Timeout")
+ waittime = options["Waittime"] if options.has_key?("Waittime")
+ else
+ prompt = options
+ end
+
+ if time_out == false
+ time_out = nil
+ end
+
+ line = ''
+ buf = ''
+ rest = ''
+ until(prompt === line and not IO::select([@sock], nil, nil, waittime))
+ unless IO::select([@sock], nil, nil, time_out)
+ raise TimeoutError, "timed-out; wait for the next data"
+ end
+ begin
+ c = @sock.sysread(1024 * 1024)
+ @dumplog.log_dump('<', c) if @options.has_key?("Dump_log")
+ if @options["Telnetmode"]
+ if Integer(c.rindex(/#{IAC}#{SE}/no)) <
+ Integer(c.rindex(/#{IAC}#{SB}/no))
+ buf = preprocess(rest + c[0 ... c.rindex(/#{IAC}#{SB}/no)])
+ rest = c[c.rindex(/#{IAC}#{SB}/no) .. -1]
+ elsif pt = c.rindex(/#{IAC}[^#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]?\z/no)
+ buf = preprocess(rest + c[0 ... pt])
+ rest = c[pt .. -1]
+ else
+ buf = preprocess(c)
+ rest = ''
+ end
+ end
+ @log.print(buf) if @options.has_key?("Output_log")
+ line.concat(buf)
+ yield buf if iterator?
+ rescue EOFError # End of file reached
+ if line == ''
+ line = nil
+ yield nil if iterator?
+ end
+ break
+ end
+ end
+ line
+ end
+
+ def write(string)
+ length = string.length
+ while 0 < length
+ IO::select(nil, [@sock])
+ @dumplog.log_dump('>', string[-length..-1]) if @options.has_key?("Dump_log")
+ length -= @sock.syswrite(string[-length..-1])
+ end
+ end
+
+ def print(string)
+ str = string.dup + "\n"
+
+ str.gsub!(/#{IAC}/no, IAC + IAC) if @options["Telnetmode"]
+
+ unless @options["Binmode"]
+ if @telnet_option["BINARY"] and @telnet_option["SGA"]
+ # IAC WILL SGA IAC DO BIN send EOL --> CR
+ str.gsub!(/\n/n, CR)
+ elsif @telnet_option["SGA"]
+ # IAC WILL SGA send EOL --> CR+NULL
+ str.gsub!(/\n/n, CR + NULL)
+ else
+ # NONE send EOL --> CR+LF
+ str.gsub!(/\n/n, EOL)
+ end
+ end
+
+ self.write(str)
+ end
+
+ def cmd(options)
+ match = @options["Prompt"]
+ time_out = @options["Timeout"]
+
+ if options.kind_of?(Hash)
+ string = options["String"]
+ match = options["Match"] if options.has_key?("Match")
+ time_out = options["Timeout"] if options.has_key?("Timeout")
+ else
+ string = options
+ end
+
+ self.print(string)
+ if iterator?
+ waitfor({"Prompt" => match, "Timeout" => time_out}){|c| yield c }
+ else
+ waitfor({"Prompt" => match, "Timeout" => time_out})
+ end
+ end
+
+ def login(options, password = nil)
+ if options.kind_of?(Hash)
+ username = options["Name"]
+ password = options["Password"]
+ else
+ username = options
+ end
+
+ if iterator?
+ line = waitfor(/login[: ]*\z/n){|c| yield c }
+ if password
+ line.concat( cmd({"String" => username,
+ "Match" => /Password[: ]*\z/n}){|c| yield c } )
+ line.concat( cmd(password){|c| yield c } )
+ else
+ line.concat( cmd(username){|c| yield c } )
+ end
+ else
+ line = waitfor(/login[: ]*\z/n)
+ if password
+ line.concat( cmd({"String" => username,
+ "Match" => /Password[: ]*\z/n}) )
+ line.concat( cmd(password) )
+ else
+ line.concat( cmd(username) )
+ end
+ end
+ line
+ end
+
+ end
+end