summaryrefslogtreecommitdiff
path: root/ruby_1_8_6/lib/net
diff options
context:
space:
mode:
Diffstat (limited to 'ruby_1_8_6/lib/net')
-rw-r--r--ruby_1_8_6/lib/net/ftp.rb923
-rw-r--r--ruby_1_8_6/lib/net/http.rb2274
-rw-r--r--ruby_1_8_6/lib/net/https.rb173
-rw-r--r--ruby_1_8_6/lib/net/imap.rb3370
-rw-r--r--ruby_1_8_6/lib/net/pop.rb879
-rw-r--r--ruby_1_8_6/lib/net/protocol.rb390
-rw-r--r--ruby_1_8_6/lib/net/smtp.rb697
-rw-r--r--ruby_1_8_6/lib/net/telnet.rb742
8 files changed, 9448 insertions, 0 deletions
diff --git a/ruby_1_8_6/lib/net/ftp.rb b/ruby_1_8_6/lib/net/ftp.rb
new file mode 100644
index 0000000000..dfbcf1499f
--- /dev/null
+++ b/ruby_1_8_6/lib/net/ftp.rb
@@ -0,0 +1,923 @@
+#
+# = net/ftp.rb - FTP Client Library
+#
+# Written by Shugo Maeda <shugo@ruby-lang.org>.
+#
+# Documentation by Gavin Sinclair, sourced from "Programming Ruby" (Hunt/Thomas)
+# and "Ruby In a Nutshell" (Matsumoto), used with permission.
+#
+# This library is distributed under the terms of the Ruby license.
+# You can freely distribute/modify this library.
+#
+# It is included in the Ruby standard library.
+#
+# See the Net::FTP class for an overview.
+#
+
+require "socket"
+require "monitor"
+
+module Net
+
+ # :stopdoc:
+ class FTPError < StandardError; end
+ class FTPReplyError < FTPError; end
+ class FTPTempError < FTPError; end
+ class FTPPermError < FTPError; end
+ class FTPProtoError < FTPError; end
+ # :startdoc:
+
+ #
+ # This class implements the File Transfer Protocol. If you have used a
+ # command-line FTP program, and are familiar with the commands, you will be
+ # able to use this class easily. Some extra features are included to take
+ # advantage of Ruby's style and strengths.
+ #
+ # == Example
+ #
+ # require 'net/ftp'
+ #
+ # === Example 1
+ #
+ # ftp = Net::FTP.new('ftp.netlab.co.jp')
+ # ftp.login
+ # files = ftp.chdir('pub/lang/ruby/contrib')
+ # files = ftp.list('n*')
+ # ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024)
+ # ftp.close
+ #
+ # === Example 2
+ #
+ # Net::FTP.open('ftp.netlab.co.jp') do |ftp|
+ # ftp.login
+ # files = ftp.chdir('pub/lang/ruby/contrib')
+ # files = ftp.list('n*')
+ # ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024)
+ # end
+ #
+ # == Major Methods
+ #
+ # The following are the methods most likely to be useful to users:
+ # - FTP.open
+ # - #getbinaryfile
+ # - #gettextfile
+ # - #putbinaryfile
+ # - #puttextfile
+ # - #chdir
+ # - #nlst
+ # - #size
+ # - #rename
+ # - #delete
+ #
+ class FTP
+ include MonitorMixin
+
+ # :stopdoc:
+ FTP_PORT = 21
+ CRLF = "\r\n"
+ DEFAULT_BLOCKSIZE = 4096
+ # :startdoc:
+
+ # When +true+, transfers are performed in binary mode. Default: +true+.
+ attr_accessor :binary
+
+ # When +true+, the connection is in passive mode. Default: +false+.
+ attr_accessor :passive
+
+ # When +true+, all traffic to and from the server is written
+ # to +$stdout+. Default: +false+.
+ attr_accessor :debug_mode
+
+ # Sets or retrieves the +resume+ status, which decides whether incomplete
+ # transfers are resumed or restarted. Default: +false+.
+ attr_accessor :resume
+
+ # The server's welcome message.
+ attr_reader :welcome
+
+ # The server's last response code.
+ attr_reader :last_response_code
+ alias lastresp last_response_code
+
+ # The server's last response.
+ attr_reader :last_response
+
+ #
+ # A synonym for <tt>FTP.new</tt>, but with a mandatory host parameter.
+ #
+ # If a block is given, it is passed the +FTP+ object, which will be closed
+ # when the block finishes, or when an exception is raised.
+ #
+ def FTP.open(host, user = nil, passwd = nil, acct = nil)
+ if block_given?
+ ftp = new(host, user, passwd, acct)
+ begin
+ yield ftp
+ ensure
+ ftp.close
+ end
+ else
+ new(host, user, passwd, acct)
+ end
+ end
+
+ #
+ # Creates and returns a new +FTP+ object. If a +host+ is given, a connection
+ # is made. Additionally, if the +user+ is given, the given user name,
+ # password, and (optionally) account are used to log in. See #login.
+ #
+ def initialize(host = nil, user = nil, passwd = nil, acct = nil)
+ super()
+ @binary = true
+ @passive = false
+ @debug_mode = false
+ @resume = false
+ if host
+ connect(host)
+ if user
+ login(user, passwd, acct)
+ end
+ end
+ end
+
+ # Obsolete
+ def return_code
+ $stderr.puts("warning: Net::FTP#return_code is obsolete and do nothing")
+ return "\n"
+ end
+
+ # Obsolete
+ def return_code=(s)
+ $stderr.puts("warning: Net::FTP#return_code= is obsolete and do nothing")
+ 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
+
+ #
+ # Establishes an FTP connection to host, optionally overriding the default
+ # port. If the environment variable +SOCKS_SERVER+ is set, sets up the
+ # connection through a SOCKS proxy. Raises an exception (typically
+ # <tt>Errno::ECONNREFUSED</tt>) if the connection cannot be established.
+ #
+ 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
+
+ #
+ # WRITEME or make private
+ #
+ def set_socket(sock, get_greeting = true)
+ synchronize do
+ @sock = sock
+ if get_greeting
+ voidresp
+ end
+ 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
+ line.sub!(/(\r\n|\n|\r)\z/n, "")
+ 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
+ @last_response = getmultiline
+ @last_response_code = @last_response[0, 3]
+ case @last_response_code
+ when /\A[123]/
+ return @last_response
+ when /\A4/
+ raise FTPTempError, @last_response
+ when /\A5/
+ raise FTPPermError, @last_response
+ else
+ raise FTPProtoError, @last_response
+ end
+ end
+ private :getresp
+
+ def voidresp
+ resp = getresp
+ if resp[0] != ?2
+ raise FTPReplyError, resp
+ end
+ end
+ private :voidresp
+
+ #
+ # Sends a command and returns the response.
+ #
+ def sendcmd(cmd)
+ synchronize do
+ putline(cmd)
+ return getresp
+ end
+ end
+
+ #
+ # Sends a command and expect a response beginning with '2'.
+ #
+ 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 = sock.addr[3]
+ 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, rest_offset = nil)
+ if @passive
+ host, port = makepasv
+ conn = open_socket(host, port)
+ if @resume and rest_offset
+ resp = sendcmd("REST " + rest_offset.to_s)
+ if resp[0] != ?3
+ raise FTPReplyError, resp
+ end
+ end
+ resp = sendcmd(cmd)
+ if resp[0] != ?1
+ raise FTPReplyError, resp
+ end
+ else
+ sock = makeport
+ if @resume and rest_offset
+ resp = sendcmd("REST " + rest_offset.to_s)
+ if resp[0] != ?3
+ raise FTPReplyError, resp
+ end
+ end
+ resp = sendcmd(cmd)
+ if resp[0] != ?1
+ raise FTPReplyError, resp
+ end
+ conn = sock.accept
+ sock.close
+ 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
+
+ #
+ # Logs in to the remote host. The session must have been previously
+ # connected. If +user+ is the string "anonymous" and the +password+ is
+ # +nil+, a password of <tt>user@host</tt> is synthesized. If the +acct+
+ # parameter is not +nil+, an FTP ACCT command is sent following the
+ # successful login. Raises an exception on error (typically
+ # <tt>Net::FTPPermError</tt>).
+ #
+ 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
+
+ #
+ # Puts the connection into binary (image) mode, issues the given command,
+ # and fetches the data returned, passing it to the associated block in
+ # chunks of +blocksize+ characters. Note that +cmd+ is a server command
+ # (such as "RETR myfile").
+ #
+ def retrbinary(cmd, blocksize, rest_offset = nil) # :yield: data
+ synchronize do
+ voidcmd("TYPE I")
+ conn = transfercmd(cmd, rest_offset)
+ loop do
+ data = conn.read(blocksize)
+ break if data == nil
+ yield(data)
+ end
+ conn.close
+ voidresp
+ end
+ end
+
+ #
+ # Puts the connection into ASCII (text) mode, issues the given command, and
+ # passes the resulting data, one line at a time, to the associated block. If
+ # no block is given, prints the lines. Note that +cmd+ is a server command
+ # (such as "RETR myfile").
+ #
+ def retrlines(cmd) # :yield: line
+ 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
+ yield(line)
+ end
+ conn.close
+ voidresp
+ end
+ end
+
+ #
+ # Puts the connection into binary (image) mode, issues the given server-side
+ # command (such as "STOR myfile"), and sends the contents of the file named
+ # +file+ to the server. If the optional block is given, it also passes it
+ # the data, in chunks of +blocksize+ characters.
+ #
+ def storbinary(cmd, file, blocksize, rest_offset = nil, &block) # :yield: data
+ if rest_offset
+ file.seek(rest_offset, IO::SEEK_SET)
+ end
+ synchronize do
+ voidcmd("TYPE I")
+ conn = transfercmd(cmd, rest_offset)
+ loop do
+ buf = file.read(blocksize)
+ break if buf == nil
+ conn.write(buf)
+ yield(buf) if block
+ end
+ conn.close
+ voidresp
+ end
+ end
+
+ #
+ # Puts the connection into ASCII (text) mode, issues the given server-side
+ # command (such as "STOR myfile"), and sends the contents of the file
+ # named +file+ to the server, one line at a time. If the optional block is
+ # given, it also passes it the lines.
+ #
+ def storlines(cmd, file, &block) # :yield: line
+ 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)
+ yield(buf) if block
+ end
+ conn.close
+ voidresp
+ end
+ end
+
+ #
+ # Retrieves +remotefile+ in binary mode, storing the result in +localfile+.
+ # If a block is supplied, it is passed the retrieved data in +blocksize+
+ # chunks.
+ #
+ def getbinaryfile(remotefile, localfile = File.basename(remotefile),
+ blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
+ if @resume
+ rest_offset = File.size?(localfile)
+ f = open(localfile, "a")
+ else
+ rest_offset = nil
+ f = open(localfile, "w")
+ end
+ begin
+ f.binmode
+ retrbinary("RETR " + remotefile, blocksize, rest_offset) do |data|
+ f.write(data)
+ yield(data) if block
+ end
+ ensure
+ f.close
+ end
+ end
+
+ #
+ # Retrieves +remotefile+ in ASCII (text) mode, storing the result in
+ # +localfile+. If a block is supplied, it is passed the retrieved data one
+ # line at a time.
+ #
+ def gettextfile(remotefile, localfile = File.basename(remotefile), &block) # :yield: line
+ f = open(localfile, "w")
+ begin
+ retrlines("RETR " + remotefile) do |line|
+ f.puts(line)
+ yield(line) if block
+ end
+ ensure
+ f.close
+ end
+ end
+
+ #
+ # Retrieves +remotefile+ in whatever mode the session is set (text or
+ # binary). See #gettextfile and #getbinaryfile.
+ #
+ def get(remotefile, localfile = File.basename(remotefile),
+ blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
+ unless @binary
+ gettextfile(remotefile, localfile, &block)
+ else
+ getbinaryfile(remotefile, localfile, blocksize, &block)
+ end
+ end
+
+ #
+ # Transfers +localfile+ to the server in binary mode, storing the result in
+ # +remotefile+. If a block is supplied, calls it, passing in the transmitted
+ # data in +blocksize+ chunks.
+ #
+ def putbinaryfile(localfile, remotefile = File.basename(localfile),
+ blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
+ if @resume
+ begin
+ rest_offset = size(remotefile)
+ rescue Net::FTPPermError
+ rest_offset = nil
+ end
+ else
+ rest_offset = nil
+ end
+ f = open(localfile)
+ begin
+ f.binmode
+ storbinary("STOR " + remotefile, f, blocksize, rest_offset, &block)
+ ensure
+ f.close
+ end
+ end
+
+ #
+ # Transfers +localfile+ to the server in ASCII (text) mode, storing the result
+ # in +remotefile+. If callback or an associated block is supplied, calls it,
+ # passing in the transmitted data one line at a time.
+ #
+ def puttextfile(localfile, remotefile = File.basename(localfile), &block) # :yield: line
+ f = open(localfile)
+ begin
+ storlines("STOR " + remotefile, f, &block)
+ ensure
+ f.close
+ end
+ end
+
+ #
+ # Transfers +localfile+ to the server in whatever mode the session is set
+ # (text or binary). See #puttextfile and #putbinaryfile.
+ #
+ def put(localfile, remotefile = File.basename(localfile),
+ blocksize = DEFAULT_BLOCKSIZE, &block)
+ unless @binary
+ puttextfile(localfile, remotefile, &block)
+ else
+ putbinaryfile(localfile, remotefile, blocksize, &block)
+ end
+ end
+
+ #
+ # Sends the ACCT command. TODO: more info.
+ #
+ def acct(account)
+ cmd = "ACCT " + account
+ voidcmd(cmd)
+ end
+
+ #
+ # Returns an array of filenames in the remote directory.
+ #
+ def nlst(dir = nil)
+ cmd = "NLST"
+ if dir
+ cmd = cmd + " " + dir
+ end
+ files = []
+ retrlines(cmd) do |line|
+ files.push(line)
+ end
+ return files
+ end
+
+ #
+ # Returns an array of file information in the directory (the output is like
+ # `ls -l`). If a block is given, it iterates through the listing.
+ #
+ def list(*args, &block) # :yield: line
+ 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
+
+ #
+ # Renames a file on the server.
+ #
+ def rename(fromname, toname)
+ resp = sendcmd("RNFR " + fromname)
+ if resp[0] != ?3
+ raise FTPReplyError, resp
+ end
+ voidcmd("RNTO " + toname)
+ end
+
+ #
+ # Deletes a file on the server.
+ #
+ 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
+
+ #
+ # Changes the (remote) directory.
+ #
+ 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
+
+ #
+ # Returns the size of the given (remote) filename.
+ #
+ 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)/ # :nodoc:
+
+ #
+ # Returns the last modification time of the (remote) file. If +local+ is
+ # +true+, it is returned as a local time, otherwise it's a UTC time.
+ #
+ 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
+
+ #
+ # Creates a remote directory.
+ #
+ def mkdir(dirname)
+ resp = sendcmd("MKD " + dirname)
+ return parse257(resp)
+ end
+
+ #
+ # Removes a remote directory.
+ #
+ def rmdir(dirname)
+ voidcmd("RMD " + dirname)
+ end
+
+ #
+ # Returns the current remote directory.
+ #
+ def pwd
+ resp = sendcmd("PWD")
+ return parse257(resp)
+ end
+ alias getdir pwd
+
+ #
+ # Returns system information.
+ #
+ def system
+ resp = sendcmd("SYST")
+ if resp[0, 3] != "215"
+ raise FTPReplyError, resp
+ end
+ return resp[4 .. -1]
+ end
+
+ #
+ # Aborts the previous command (ABOR command).
+ #
+ 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
+
+ #
+ # Returns the status (STAT command).
+ #
+ def status
+ line = "STAT" + CRLF
+ print "put: STAT\n" if @debug_mode
+ @sock.send(line, Socket::MSG_OOB)
+ return getresp
+ end
+
+ #
+ # Issues the MDTM command. TODO: more info.
+ #
+ def mdtm(filename)
+ resp = sendcmd("MDTM " + filename)
+ if resp[0, 3] == "213"
+ return resp[3 .. -1].strip
+ end
+ end
+
+ #
+ # Issues the HELP command.
+ #
+ def help(arg = nil)
+ cmd = "HELP"
+ if arg
+ cmd = cmd + " " + arg
+ end
+ sendcmd(cmd)
+ end
+
+ #
+ # Exits the FTP session.
+ #
+ def quit
+ voidcmd("QUIT")
+ end
+
+ #
+ # Issues a NOOP command.
+ #
+ def noop
+ voidcmd("NOOP")
+ end
+
+ #
+ # Issues a SITE command.
+ #
+ def site(arg)
+ cmd = "SITE " + arg
+ voidcmd(cmd)
+ end
+
+ #
+ # Closes the connection. Further operations are impossible until you open
+ # a new connection with #connect.
+ #
+ def close
+ @sock.close if @sock and not @sock.closed?
+ end
+
+ #
+ # Returns +true+ iff the connection is closed.
+ #
+ 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 :parse229
+
+ 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
+
+
+# Documentation comments:
+# - sourced from pickaxe and nutshell, with improvements (hopefully)
+# - three methods should be private (search WRITEME)
+# - two methods need more information (search TODO)
diff --git a/ruby_1_8_6/lib/net/http.rb b/ruby_1_8_6/lib/net/http.rb
new file mode 100644
index 0000000000..46d95b27b4
--- /dev/null
+++ b/ruby_1_8_6/lib/net/http.rb
@@ -0,0 +1,2274 @@
+#
+# = net/http.rb
+#
+# Copyright (c) 1999-2006 Yukihiro Matsumoto
+# Copyright (c) 1999-2006 Minero Aoki
+# Copyright (c) 2001 GOTOU Yuuzou
+#
+# Written and maintained by Minero Aoki <aamine@loveruby.net>.
+# HTTPS support added by GOTOU Yuuzou <gotoyuzo@notwork.org>.
+#
+# This file is derived from "http-access.rb".
+#
+# Documented by Minero Aoki; converted to RDoc by William Webber.
+#
+# This program is free software. You can re-distribute and/or
+# modify this program under the same terms of ruby itself ---
+# Ruby Distribution License or GNU General Public License.
+#
+# See Net::HTTP for an overview and examples.
+#
+# NOTE: You can find Japanese version of this document here:
+# http://www.ruby-lang.org/ja/man/?cmd=view;name=net%2Fhttp.rb
+#
+#--
+# $Id$
+#++
+
+require 'net/protocol'
+require 'uri'
+
+module Net #:nodoc:
+
+ # :stopdoc:
+ class HTTPBadResponse < StandardError; end
+ class HTTPHeaderSyntaxError < StandardError; end
+ # :startdoc:
+
+ # == What Is This Library?
+ #
+ # This library provides your program functions to access WWW
+ # documents via HTTP, Hyper Text Transfer Protocol version 1.1.
+ # For details of HTTP, refer [RFC2616]
+ # (http://www.ietf.org/rfc/rfc2616.txt).
+ #
+ # == Examples
+ #
+ # === Getting Document From WWW Server
+ #
+ # Example #1: Simple GET+print
+ #
+ # require 'net/http'
+ # Net::HTTP.get_print 'www.example.com', '/index.html'
+ #
+ # Example #2: Simple GET+print by URL
+ #
+ # require 'net/http'
+ # require 'uri'
+ # Net::HTTP.get_print URI.parse('http://www.example.com/index.html')
+ #
+ # Example #3: More generic GET+print
+ #
+ # require 'net/http'
+ # require 'uri'
+ #
+ # url = URI.parse('http://www.example.com/index.html')
+ # res = Net::HTTP.start(url.host, url.port) {|http|
+ # http.get('/index.html')
+ # }
+ # puts res.body
+ #
+ # Example #4: More generic GET+print
+ #
+ # require 'net/http'
+ #
+ # url = URI.parse('http://www.example.com/index.html')
+ # req = Net::HTTP::Get.new(url.path)
+ # res = Net::HTTP.start(url.host, url.port) {|http|
+ # http.request(req)
+ # }
+ # puts res.body
+ #
+ # === Posting Form Data
+ #
+ # require 'net/http'
+ # require 'uri'
+ #
+ # #1: Simple POST
+ # res = Net::HTTP.post_form(URI.parse('http://www.example.com/search.cgi'),
+ # {'q'=>'ruby', 'max'=>'50'})
+ # puts res.body
+ #
+ # #2: POST with basic authentication
+ # res = Net::HTTP.post_form(URI.parse('http://jack:pass@www.example.com/todo.cgi'),
+ # {'from'=>'2005-01-01', 'to'=>'2005-03-31'})
+ # puts res.body
+ #
+ # #3: Detailed control
+ # url = URI.parse('http://www.example.com/todo.cgi')
+ # req = Net::HTTP::Post.new(url.path)
+ # req.basic_auth 'jack', 'pass'
+ # req.set_form_data({'from'=>'2005-01-01', 'to'=>'2005-03-31'}, ';')
+ # res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }
+ # case res
+ # when Net::HTTPSuccess, Net::HTTPRedirection
+ # # OK
+ # else
+ # res.error!
+ # end
+ #
+ # === Accessing via Proxy
+ #
+ # Net::HTTP.Proxy creates http proxy class. It has same
+ # methods of Net::HTTP but its instances always connect to
+ # proxy, instead of given host.
+ #
+ # require 'net/http'
+ #
+ # proxy_addr = 'your.proxy.host'
+ # proxy_port = 8080
+ # :
+ # Net::HTTP::Proxy(proxy_addr, proxy_port).start('www.example.com') {|http|
+ # # always connect to your.proxy.addr:8080
+ # :
+ # }
+ #
+ # Since Net::HTTP.Proxy returns Net::HTTP itself when proxy_addr is nil,
+ # there's no need to change code if there's proxy or not.
+ #
+ # There are two additional parameters in Net::HTTP.Proxy which allow to
+ # specify proxy user name and password:
+ #
+ # Net::HTTP::Proxy(proxy_addr, proxy_port, proxy_user = nil, proxy_pass = nil)
+ #
+ # You may use them to work with authorization-enabled proxies:
+ #
+ # require 'net/http'
+ # require 'uri'
+ #
+ # proxy_host = 'your.proxy.host'
+ # proxy_port = 8080
+ # uri = URI.parse(ENV['http_proxy'])
+ # proxy_user, proxy_pass = uri.userinfo.split(/:/) if uri.userinfo
+ # Net::HTTP::Proxy(proxy_host, proxy_port,
+ # proxy_user, proxy_pass).start('www.example.com') {|http|
+ # # always connect to your.proxy.addr:8080 using specified username and password
+ # :
+ # }
+ #
+ # Note that net/http never rely on HTTP_PROXY environment variable.
+ # If you want to use proxy, set it explicitly.
+ #
+ # === Following Redirection
+ #
+ # require 'net/http'
+ # require 'uri'
+ #
+ # def fetch(uri_str, limit = 10)
+ # # You should choose better exception.
+ # raise ArgumentError, 'HTTP redirect too deep' if limit == 0
+ #
+ # response = Net::HTTP.get_response(URI.parse(uri_str))
+ # case response
+ # when Net::HTTPSuccess then response
+ # when Net::HTTPRedirection then fetch(response['location'], limit - 1)
+ # else
+ # response.error!
+ # end
+ # end
+ #
+ # print fetch('http://www.ruby-lang.org')
+ #
+ # Net::HTTPSuccess and Net::HTTPRedirection is a HTTPResponse class.
+ # All HTTPResponse objects belong to its own response class which
+ # indicate HTTP result status. For details of response classes,
+ # see section "HTTP Response Classes".
+ #
+ # === Basic Authentication
+ #
+ # require 'net/http'
+ #
+ # Net::HTTP.start('www.example.com') {|http|
+ # req = Net::HTTP::Get.new('/secret-page.html')
+ # req.basic_auth 'account', 'password'
+ # response = http.request(req)
+ # print response.body
+ # }
+ #
+ # === HTTP Request Classes
+ #
+ # Here is HTTP request class hierarchy.
+ #
+ # Net::HTTPRequest
+ # Net::HTTP::Get
+ # Net::HTTP::Head
+ # Net::HTTP::Post
+ # Net::HTTP::Put
+ # Net::HTTP::Proppatch
+ # Net::HTTP::Lock
+ # Net::HTTP::Unlock
+ # Net::HTTP::Options
+ # Net::HTTP::Propfind
+ # Net::HTTP::Delete
+ # Net::HTTP::Move
+ # Net::HTTP::Copy
+ # Net::HTTP::Mkcol
+ # Net::HTTP::Trace
+ #
+ # === HTTP Response Classes
+ #
+ # Here is HTTP response class hierarchy.
+ # All classes are defined in Net module.
+ #
+ # HTTPResponse
+ # HTTPUnknownResponse
+ # HTTPInformation # 1xx
+ # HTTPContinue # 100
+ # HTTPSwitchProtocl # 101
+ # HTTPSuccess # 2xx
+ # HTTPOK # 200
+ # HTTPCreated # 201
+ # HTTPAccepted # 202
+ # HTTPNonAuthoritativeInformation # 203
+ # HTTPNoContent # 204
+ # HTTPResetContent # 205
+ # HTTPPartialContent # 206
+ # HTTPRedirection # 3xx
+ # HTTPMultipleChoice # 300
+ # HTTPMovedPermanently # 301
+ # HTTPFound # 302
+ # HTTPSeeOther # 303
+ # HTTPNotModified # 304
+ # HTTPUseProxy # 305
+ # HTTPTemporaryRedirect # 307
+ # HTTPClientError # 4xx
+ # HTTPBadRequest # 400
+ # HTTPUnauthorized # 401
+ # HTTPPaymentRequired # 402
+ # HTTPForbidden # 403
+ # HTTPNotFound # 404
+ # HTTPMethodNotAllowed # 405
+ # HTTPNotAcceptable # 406
+ # HTTPProxyAuthenticationRequired # 407
+ # HTTPRequestTimeOut # 408
+ # HTTPConflict # 409
+ # HTTPGone # 410
+ # HTTPLengthRequired # 411
+ # HTTPPreconditionFailed # 412
+ # HTTPRequestEntityTooLarge # 413
+ # HTTPRequestURITooLong # 414
+ # HTTPUnsupportedMediaType # 415
+ # HTTPRequestedRangeNotSatisfiable # 416
+ # HTTPExpectationFailed # 417
+ # HTTPServerError # 5xx
+ # HTTPInternalServerError # 500
+ # HTTPNotImplemented # 501
+ # HTTPBadGateway # 502
+ # HTTPServiceUnavailable # 503
+ # HTTPGatewayTimeOut # 504
+ # HTTPVersionNotSupported # 505
+ #
+ # == Switching Net::HTTP versions
+ #
+ # You can use net/http.rb 1.1 features (bundled with Ruby 1.6)
+ # by calling HTTP.version_1_1. Calling Net::HTTP.version_1_2
+ # allows you to use 1.2 features again.
+ #
+ # # example
+ # Net::HTTP.start {|http1| ...(http1 has 1.2 features)... }
+ #
+ # Net::HTTP.version_1_1
+ # Net::HTTP.start {|http2| ...(http2 has 1.1 features)... }
+ #
+ # Net::HTTP.version_1_2
+ # Net::HTTP.start {|http3| ...(http3 has 1.2 features)... }
+ #
+ # This function is NOT thread-safe.
+ #
+ class HTTP < Protocol
+
+ # :stopdoc:
+ Revision = %q$Revision$.split[1]
+ HTTPVersion = '1.1'
+ @newimpl = true
+ # :startdoc:
+
+ # Turns on net/http 1.2 (ruby 1.8) features.
+ # Defaults to ON in ruby 1.8.
+ #
+ # I strongly recommend to call this method always.
+ #
+ # require 'net/http'
+ # Net::HTTP.version_1_2
+ #
+ def HTTP.version_1_2
+ @newimpl = true
+ end
+
+ # Turns on net/http 1.1 (ruby 1.6) features.
+ # Defaults to OFF in ruby 1.8.
+ def HTTP.version_1_1
+ @newimpl = false
+ end
+
+ # true if net/http is in version 1.2 mode.
+ # Defaults to true.
+ def HTTP.version_1_2?
+ @newimpl
+ end
+
+ # true if net/http is in version 1.1 compatible mode.
+ # Defaults to true.
+ def HTTP.version_1_1?
+ not @newimpl
+ end
+
+ class << HTTP
+ alias is_version_1_1? version_1_1? #:nodoc:
+ alias is_version_1_2? version_1_2? #:nodoc:
+ end
+
+ #
+ # short cut methods
+ #
+
+ #
+ # Get body from target and output it to +$stdout+. The
+ # target can either be specified as (+uri+), or as
+ # (+host+, +path+, +port+ = 80); so:
+ #
+ # Net::HTTP.get_print URI.parse('http://www.example.com/index.html')
+ #
+ # or:
+ #
+ # Net::HTTP.get_print 'www.example.com', '/index.html'
+ #
+ def HTTP.get_print(uri_or_host, path = nil, port = nil)
+ get_response(uri_or_host, path, port) {|res|
+ res.read_body do |chunk|
+ $stdout.print chunk
+ end
+ }
+ nil
+ end
+
+ # Send a GET request to the target and return the response
+ # as a string. The target can either be specified as
+ # (+uri+), or as (+host+, +path+, +port+ = 80); so:
+ #
+ # print Net::HTTP.get(URI.parse('http://www.example.com/index.html'))
+ #
+ # or:
+ #
+ # print Net::HTTP.get('www.example.com', '/index.html')
+ #
+ def HTTP.get(uri_or_host, path = nil, port = nil)
+ get_response(uri_or_host, path, port).body
+ end
+
+ # Send a GET request to the target and return the response
+ # as a Net::HTTPResponse object. The target can either be specified as
+ # (+uri+), or as (+host+, +path+, +port+ = 80); so:
+ #
+ # res = Net::HTTP.get_response(URI.parse('http://www.example.com/index.html'))
+ # print res.body
+ #
+ # or:
+ #
+ # res = Net::HTTP.get_response('www.example.com', '/index.html')
+ # print res.body
+ #
+ def HTTP.get_response(uri_or_host, path = nil, port = nil, &block)
+ if path
+ host = uri_or_host
+ new(host, port || HTTP.default_port).start {|http|
+ return http.request_get(path, &block)
+ }
+ else
+ uri = uri_or_host
+ new(uri.host, uri.port).start {|http|
+ return http.request_get(uri.request_uri, &block)
+ }
+ end
+ end
+
+ # Posts HTML form data to the +URL+.
+ # Form data must be represented as a Hash of String to String, e.g:
+ #
+ # { "cmd" => "search", "q" => "ruby", "max" => "50" }
+ #
+ # This method also does Basic Authentication iff +URL+.user exists.
+ #
+ # Example:
+ #
+ # require 'net/http'
+ # require 'uri'
+ #
+ # HTTP.post_form URI.parse('http://www.example.com/search.cgi'),
+ # { "q" => "ruby", "max" => "50" }
+ #
+ def HTTP.post_form(url, params)
+ req = Post.new(url.path)
+ req.form_data = params
+ req.basic_auth url.user, url.password if url.user
+ new(url.host, url.port).start {|http|
+ http.request(req)
+ }
+ end
+
+ #
+ # HTTP session management
+ #
+
+ # The default port to use for HTTP requests; defaults to 80.
+ def HTTP.default_port
+ http_default_port()
+ end
+
+ # The default port to use for HTTP requests; defaults to 80.
+ def HTTP.http_default_port
+ 80
+ end
+
+ # The default port to use for HTTPS requests; defaults to 443.
+ def HTTP.https_default_port
+ 443
+ end
+
+ def HTTP.socket_type #:nodoc: obsolete
+ BufferedIO
+ end
+
+ # creates a new Net::HTTP object and opens its TCP connection and
+ # HTTP session. If the optional block is given, the newly
+ # created Net::HTTP object is passed to it and closed when the
+ # block finishes. In this case, the return value of this method
+ # is the return value of the block. If no block is given, the
+ # return value of this method is the newly created Net::HTTP object
+ # itself, and the caller is responsible for closing it upon completion.
+ def HTTP.start(address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil, &block) # :yield: +http+
+ new(address, port, p_addr, p_port, p_user, p_pass).start(&block)
+ end
+
+ class << HTTP
+ alias newobj new
+ end
+
+ # Creates a new Net::HTTP object.
+ # If +proxy_addr+ is given, creates an Net::HTTP object with proxy support.
+ # This method does not open the TCP connection.
+ def HTTP.new(address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil)
+ h = Proxy(p_addr, p_port, p_user, p_pass).newobj(address, port)
+ h.instance_eval {
+ @newimpl = ::Net::HTTP.version_1_2?
+ }
+ h
+ end
+
+ # Creates a new Net::HTTP object for the specified +address+.
+ # This method does not open the TCP connection.
+ def initialize(address, port = nil)
+ @address = address
+ @port = (port || HTTP.default_port)
+ @curr_http_version = HTTPVersion
+ @seems_1_0_server = false
+ @close_on_empty_response = false
+ @socket = nil
+ @started = false
+ @open_timeout = nil
+ @read_timeout = 60
+ @debug_output = nil
+ @use_ssl = false
+ @ssl_context = nil
+ end
+
+ def inspect
+ "#<#{self.class} #{@address}:#{@port} open=#{started?}>"
+ end
+
+ # *WARNING* This method causes serious security hole.
+ # Never use this method in production code.
+ #
+ # Set an output stream for debugging.
+ #
+ # http = Net::HTTP.new
+ # http.set_debug_output $stderr
+ # http.start { .... }
+ #
+ def set_debug_output(output)
+ warn 'Net::HTTP#set_debug_output called after HTTP started' if started?
+ @debug_output = output
+ end
+
+ # The host name to connect to.
+ attr_reader :address
+
+ # The port number to connect to.
+ attr_reader :port
+
+ # Seconds to wait until connection is opened.
+ # If the HTTP object cannot open a connection in this many seconds,
+ # it raises a TimeoutError exception.
+ attr_accessor :open_timeout
+
+ # Seconds to wait until reading one block (by one read(2) call).
+ # If the HTTP object cannot open a connection in this many seconds,
+ # it raises a TimeoutError exception.
+ attr_reader :read_timeout
+
+ # Setter for the read_timeout attribute.
+ def read_timeout=(sec)
+ @socket.read_timeout = sec if @socket
+ @read_timeout = sec
+ end
+
+ # returns true if the HTTP session is started.
+ def started?
+ @started
+ end
+
+ alias active? started? #:nodoc: obsolete
+
+ attr_accessor :close_on_empty_response
+
+ # returns true if use SSL/TLS with HTTP.
+ def use_ssl?
+ false # redefined in net/https
+ end
+
+ # Opens TCP connection and HTTP session.
+ #
+ # When this method is called with block, gives a HTTP object
+ # to the block and closes the TCP connection / HTTP session
+ # after the block executed.
+ #
+ # When called with a block, returns the return value of the
+ # block; otherwise, returns self.
+ #
+ def start # :yield: http
+ raise IOError, 'HTTP session already opened' if @started
+ if block_given?
+ begin
+ do_start
+ return yield(self)
+ ensure
+ do_finish
+ end
+ end
+ do_start
+ self
+ end
+
+ def do_start
+ connect
+ @started = true
+ end
+ private :do_start
+
+ def connect
+ D "opening connection to #{conn_address()}..."
+ s = timeout(@open_timeout) { TCPSocket.open(conn_address(), conn_port()) }
+ D "opened"
+ if use_ssl?
+ unless @ssl_context.verify_mode
+ warn "warning: peer certificate won't be verified in this SSL session"
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ end
+ s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
+ s.sync_close = true
+ end
+ @socket = BufferedIO.new(s)
+ @socket.read_timeout = @read_timeout
+ @socket.debug_output = @debug_output
+ if use_ssl?
+ if proxy?
+ @socket.writeline sprintf('CONNECT %s:%s HTTP/%s',
+ @address, @port, HTTPVersion)
+ @socket.writeline "Host: #{@address}:#{@port}"
+ if proxy_user
+ credential = ["#{proxy_user}:#{proxy_pass}"].pack('m')
+ credential.delete!("\r\n")
+ @socket.writeline "Proxy-Authorization: Basic #{credential}"
+ end
+ @socket.writeline ''
+ HTTPResponse.read_new(@socket).value
+ end
+ s.connect
+ end
+ on_connect
+ end
+ private :connect
+
+ def on_connect
+ end
+ private :on_connect
+
+ # Finishes HTTP session and closes TCP connection.
+ # Raises IOError if not started.
+ def finish
+ raise IOError, 'HTTP session not yet started' unless started?
+ do_finish
+ end
+
+ def do_finish
+ @started = false
+ @socket.close if @socket and not @socket.closed?
+ @socket = nil
+ end
+ private :do_finish
+
+ #
+ # proxy
+ #
+
+ public
+
+ # no proxy
+ @is_proxy_class = false
+ @proxy_addr = nil
+ @proxy_port = nil
+ @proxy_user = nil
+ @proxy_pass = nil
+
+ # Creates an HTTP proxy class.
+ # Arguments are address/port of proxy host and username/password
+ # if authorization on proxy server is required.
+ # You can replace the HTTP class with created proxy class.
+ #
+ # If ADDRESS is nil, this method returns self (Net::HTTP).
+ #
+ # # Example
+ # proxy_class = Net::HTTP::Proxy('proxy.example.com', 8080)
+ # :
+ # proxy_class.start('www.ruby-lang.org') {|http|
+ # # connecting proxy.foo.org:8080
+ # :
+ # }
+ #
+ def HTTP.Proxy(p_addr, p_port = nil, p_user = nil, p_pass = nil)
+ return self unless p_addr
+ delta = ProxyDelta
+ proxyclass = Class.new(self)
+ proxyclass.module_eval {
+ include delta
+ # with proxy
+ @is_proxy_class = true
+ @proxy_address = p_addr
+ @proxy_port = p_port || default_port()
+ @proxy_user = p_user
+ @proxy_pass = p_pass
+ }
+ proxyclass
+ end
+
+ class << HTTP
+ # returns true if self is a class which was created by HTTP::Proxy.
+ def proxy_class?
+ @is_proxy_class
+ end
+
+ attr_reader :proxy_address
+ attr_reader :proxy_port
+ attr_reader :proxy_user
+ attr_reader :proxy_pass
+ end
+
+ # True if self is a HTTP proxy class.
+ def proxy?
+ self.class.proxy_class?
+ end
+
+ # Address of proxy host. If self does not use a proxy, nil.
+ def proxy_address
+ self.class.proxy_address
+ end
+
+ # Port number of proxy host. If self does not use a proxy, nil.
+ def proxy_port
+ self.class.proxy_port
+ end
+
+ # User name for accessing proxy. If self does not use a proxy, nil.
+ def proxy_user
+ self.class.proxy_user
+ end
+
+ # User password for accessing proxy. If self does not use a proxy, nil.
+ def proxy_pass
+ self.class.proxy_pass
+ end
+
+ alias proxyaddr proxy_address #:nodoc: obsolete
+ alias proxyport proxy_port #:nodoc: obsolete
+
+ private
+
+ # without proxy
+
+ def conn_address
+ address()
+ end
+
+ def conn_port
+ port()
+ end
+
+ def edit_path(path)
+ path
+ end
+
+ module ProxyDelta #:nodoc: internal use only
+ private
+
+ def conn_address
+ proxy_address()
+ end
+
+ def conn_port
+ proxy_port()
+ end
+
+ def edit_path(path)
+ use_ssl? ? path : "http://#{addr_port()}#{path}"
+ end
+ end
+
+ #
+ # HTTP operations
+ #
+
+ public
+
+ # Gets data from +path+ on the connected-to host.
+ # +header+ must be a Hash like { 'Accept' => '*/*', ... }.
+ #
+ # In version 1.1 (ruby 1.6), this method returns a pair of objects,
+ # a Net::HTTPResponse object and the entity body string.
+ # In version 1.2 (ruby 1.8), this method returns a Net::HTTPResponse
+ # object.
+ #
+ # If called with a block, yields each fragment of the
+ # entity body in turn as a string as it is read from
+ # the socket. Note that in this case, the returned response
+ # object will *not* contain a (meaningful) body.
+ #
+ # +dest+ argument is obsolete.
+ # It still works but you must not use it.
+ #
+ # In version 1.1, this method might raise an exception for
+ # 3xx (redirect). In this case you can get a HTTPResponse object
+ # by "anException.response".
+ #
+ # In version 1.2, this method never raises exception.
+ #
+ # # version 1.1 (bundled with Ruby 1.6)
+ # response, body = http.get('/index.html')
+ #
+ # # version 1.2 (bundled with Ruby 1.8 or later)
+ # response = http.get('/index.html')
+ #
+ # # using block
+ # File.open('result.txt', 'w') {|f|
+ # http.get('/~foo/') do |str|
+ # f.write str
+ # end
+ # }
+ #
+ def get(path, initheader = nil, dest = nil, &block) # :yield: +body_segment+
+ res = nil
+ request(Get.new(path, initheader)) {|r|
+ r.read_body dest, &block
+ res = r
+ }
+ unless @newimpl
+ res.value
+ return res, res.body
+ end
+
+ res
+ end
+
+ # Gets only the header from +path+ on the connected-to host.
+ # +header+ is a Hash like { 'Accept' => '*/*', ... }.
+ #
+ # This method returns a Net::HTTPResponse object.
+ #
+ # In version 1.1, this method might raise an exception for
+ # 3xx (redirect). On the case you can get a HTTPResponse object
+ # by "anException.response".
+ # In version 1.2, this method never raises an exception.
+ #
+ # response = nil
+ # Net::HTTP.start('some.www.server', 80) {|http|
+ # response = http.head('/index.html')
+ # }
+ # p response['content-type']
+ #
+ def head(path, initheader = nil)
+ res = request(Head.new(path, initheader))
+ res.value unless @newimpl
+ res
+ end
+
+ # Posts +data+ (must be a String) to +path+. +header+ must be a Hash
+ # like { 'Accept' => '*/*', ... }.
+ #
+ # In version 1.1 (ruby 1.6), this method returns a pair of objects, a
+ # Net::HTTPResponse object and an entity body string.
+ # In version 1.2 (ruby 1.8), this method returns a Net::HTTPResponse object.
+ #
+ # If called with a block, yields each fragment of the
+ # entity body in turn as a string as it are read from
+ # the socket. Note that in this case, the returned response
+ # object will *not* contain a (meaningful) body.
+ #
+ # +dest+ argument is obsolete.
+ # It still works but you must not use it.
+ #
+ # In version 1.1, this method might raise an exception for
+ # 3xx (redirect). In this case you can get an HTTPResponse object
+ # by "anException.response".
+ # In version 1.2, this method never raises exception.
+ #
+ # # version 1.1
+ # response, body = http.post('/cgi-bin/search.rb', 'query=foo')
+ #
+ # # version 1.2
+ # response = http.post('/cgi-bin/search.rb', 'query=foo')
+ #
+ # # using block
+ # File.open('result.txt', 'w') {|f|
+ # http.post('/cgi-bin/search.rb', 'query=foo') do |str|
+ # f.write str
+ # end
+ # }
+ #
+ # You should set Content-Type: header field for POST.
+ # If no Content-Type: field given, this method uses
+ # "application/x-www-form-urlencoded" by default.
+ #
+ def post(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+
+ res = nil
+ request(Post.new(path, initheader), data) {|r|
+ r.read_body dest, &block
+ res = r
+ }
+ unless @newimpl
+ res.value
+ return res, res.body
+ end
+ res
+ end
+
+ def put(path, data, initheader = nil) #:nodoc:
+ res = request(Put.new(path, initheader), data)
+ res.value unless @newimpl
+ res
+ end
+
+ # Sends a PROPPATCH request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def proppatch(path, body, initheader = nil)
+ request(Proppatch.new(path, initheader), body)
+ end
+
+ # Sends a LOCK request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def lock(path, body, initheader = nil)
+ request(Lock.new(path, initheader), body)
+ end
+
+ # Sends a UNLOCK request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def unlock(path, body, initheader = nil)
+ request(Unlock.new(path, initheader), body)
+ end
+
+ # Sends a OPTIONS request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def options(path, initheader = nil)
+ request(Options.new(path, initheader))
+ end
+
+ # Sends a PROPFIND request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def propfind(path, body = nil, initheader = {'Depth' => '0'})
+ request(Propfind.new(path, initheader), body)
+ end
+
+ # Sends a DELETE request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def delete(path, initheader = {'Depth' => 'Infinity'})
+ request(Delete.new(path, initheader))
+ end
+
+ # Sends a MOVE request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def move(path, initheader = nil)
+ request(Move.new(path, initheader))
+ end
+
+ # Sends a COPY request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def copy(path, initheader = nil)
+ request(Copy.new(path, initheader))
+ end
+
+ # Sends a MKCOL request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def mkcol(path, body = nil, initheader = nil)
+ request(Mkcol.new(path, initheader), body)
+ end
+
+ # Sends a TRACE request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def trace(path, initheader = nil)
+ request(Trace.new(path, initheader))
+ end
+
+ # Sends a GET request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ #
+ # When called with a block, yields an HTTPResponse object.
+ # The body of this response will not have been read yet;
+ # the caller can process it using HTTPResponse#read_body,
+ # if desired.
+ #
+ # Returns the response.
+ #
+ # This method never raises Net::* exceptions.
+ #
+ # response = http.request_get('/index.html')
+ # # The entity body is already read here.
+ # p response['content-type']
+ # puts response.body
+ #
+ # # using block
+ # http.request_get('/index.html') {|response|
+ # p response['content-type']
+ # response.read_body do |str| # read body now
+ # print str
+ # end
+ # }
+ #
+ def request_get(path, initheader = nil, &block) # :yield: +response+
+ request(Get.new(path, initheader), &block)
+ end
+
+ # Sends a HEAD request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ #
+ # Returns the response.
+ #
+ # This method never raises Net::* exceptions.
+ #
+ # response = http.request_head('/index.html')
+ # p response['content-type']
+ #
+ def request_head(path, initheader = nil, &block)
+ request(Head.new(path, initheader), &block)
+ end
+
+ # Sends a POST request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ #
+ # When called with a block, yields an HTTPResponse object.
+ # The body of this response will not have been read yet;
+ # the caller can process it using HTTPResponse#read_body,
+ # if desired.
+ #
+ # Returns the response.
+ #
+ # This method never raises Net::* exceptions.
+ #
+ # # example
+ # response = http.request_post('/cgi-bin/nice.rb', 'datadatadata...')
+ # p response.status
+ # puts response.body # body is already read
+ #
+ # # using block
+ # http.request_post('/cgi-bin/nice.rb', 'datadatadata...') {|response|
+ # p response.status
+ # p response['content-type']
+ # response.read_body do |str| # read body now
+ # print str
+ # end
+ # }
+ #
+ def request_post(path, data, initheader = nil, &block) # :yield: +response+
+ request Post.new(path, initheader), data, &block
+ end
+
+ def request_put(path, data, initheader = nil, &block) #:nodoc:
+ request Put.new(path, initheader), data, &block
+ end
+
+ alias get2 request_get #:nodoc: obsolete
+ alias head2 request_head #:nodoc: obsolete
+ alias post2 request_post #:nodoc: obsolete
+ alias put2 request_put #:nodoc: obsolete
+
+
+ # Sends an HTTP request to the HTTP server.
+ # This method also sends DATA string if DATA is given.
+ #
+ # Returns a HTTPResponse object.
+ #
+ # This method never raises Net::* exceptions.
+ #
+ # response = http.send_request('GET', '/index.html')
+ # puts response.body
+ #
+ def send_request(name, path, data = nil, header = nil)
+ r = HTTPGenericRequest.new(name,(data ? true : false),true,path,header)
+ request r, data
+ end
+
+ # Sends an HTTPRequest object REQUEST to the HTTP server.
+ # This method also sends DATA string if REQUEST is a post/put request.
+ # Giving DATA for get/head request causes ArgumentError.
+ #
+ # When called with a block, yields an HTTPResponse object.
+ # The body of this response will not have been read yet;
+ # the caller can process it using HTTPResponse#read_body,
+ # if desired.
+ #
+ # Returns a HTTPResponse object.
+ #
+ # This method never raises Net::* exceptions.
+ #
+ def request(req, body = nil, &block) # :yield: +response+
+ unless started?
+ start {
+ req['connection'] ||= 'close'
+ return request(req, body, &block)
+ }
+ end
+ if proxy_user()
+ unless use_ssl?
+ req.proxy_basic_auth proxy_user(), proxy_pass()
+ end
+ end
+
+ req.set_body_internal body
+ begin_transport req
+ req.exec @socket, @curr_http_version, edit_path(req.path)
+ begin
+ res = HTTPResponse.read_new(@socket)
+ end while res.kind_of?(HTTPContinue)
+ res.reading_body(@socket, req.response_body_permitted?) {
+ yield res if block_given?
+ }
+ end_transport req, res
+
+ res
+ end
+
+ private
+
+ def begin_transport(req)
+ if @socket.closed?
+ connect
+ end
+ if @seems_1_0_server
+ req['connection'] ||= 'close'
+ end
+ if not req.response_body_permitted? and @close_on_empty_response
+ req['connection'] ||= 'close'
+ end
+ req['host'] ||= addr_port()
+ end
+
+ def end_transport(req, res)
+ @curr_http_version = res.http_version
+ if not res.body and @close_on_empty_response
+ D 'Conn close'
+ @socket.close
+ elsif keep_alive?(req, res)
+ D 'Conn keep-alive'
+ if @socket.closed?
+ D 'Conn (but seems 1.0 server)'
+ @seems_1_0_server = true
+ end
+ else
+ D 'Conn close'
+ @socket.close
+ end
+ end
+
+ def keep_alive?(req, res)
+ return false if /close/i =~ req['connection'].to_s
+ return false if @seems_1_0_server
+ return true if /keep-alive/i =~ res['connection'].to_s
+ return false if /close/i =~ res['connection'].to_s
+ return true if /keep-alive/i =~ res['proxy-connection'].to_s
+ return false if /close/i =~ res['proxy-connection'].to_s
+ (@curr_http_version == '1.1')
+ end
+
+ #
+ # utils
+ #
+
+ private
+
+ def addr_port
+ if use_ssl?
+ address() + (port == HTTP.https_default_port ? '' : ":#{port()}")
+ else
+ address() + (port == HTTP.http_default_port ? '' : ":#{port()}")
+ end
+ end
+
+ def D(msg)
+ return unless @debug_output
+ @debug_output << msg
+ @debug_output << "\n"
+ end
+
+ end
+
+ HTTPSession = HTTP
+
+
+ #
+ # Header module.
+ #
+ # Provides access to @header in the mixed-into class as a hash-like
+ # object, except with case-insensitive keys. Also provides
+ # methods for accessing commonly-used header values in a more
+ # convenient format.
+ #
+ module HTTPHeader
+
+ def initialize_http_header(initheader)
+ @header = {}
+ return unless initheader
+ initheader.each do |key, value|
+ warn "net/http: warning: duplicated HTTP header: #{key}" if key?(key) and $VERBOSE
+ @header[key.downcase] = [value.strip]
+ end
+ end
+
+ def size #:nodoc: obsolete
+ @header.size
+ end
+
+ alias length size #:nodoc: obsolete
+
+ # Returns the header field corresponding to the case-insensitive key.
+ # For example, a key of "Content-Type" might return "text/html"
+ def [](key)
+ a = @header[key.downcase] or return nil
+ a.join(', ')
+ end
+
+ # Sets the header field corresponding to the case-insensitive key.
+ def []=(key, val)
+ unless val
+ @header.delete key.downcase
+ return val
+ end
+ @header[key.downcase] = [val]
+ end
+
+ # [Ruby 1.8.3]
+ # Adds header field instead of replace.
+ # Second argument +val+ must be a String.
+ # See also #[]=, #[] and #get_fields.
+ #
+ # request.add_field 'X-My-Header', 'a'
+ # p request['X-My-Header'] #=> "a"
+ # p request.get_fields('X-My-Header') #=> ["a"]
+ # request.add_field 'X-My-Header', 'b'
+ # p request['X-My-Header'] #=> "a, b"
+ # p request.get_fields('X-My-Header') #=> ["a", "b"]
+ # request.add_field 'X-My-Header', 'c'
+ # p request['X-My-Header'] #=> "a, b, c"
+ # p request.get_fields('X-My-Header') #=> ["a", "b", "c"]
+ #
+ def add_field(key, val)
+ if @header.key?(key.downcase)
+ @header[key.downcase].push val
+ else
+ @header[key.downcase] = [val]
+ end
+ end
+
+ # [Ruby 1.8.3]
+ # Returns an array of header field strings corresponding to the
+ # case-insensitive +key+. This method allows you to get duplicated
+ # header fields without any processing. See also #[].
+ #
+ # p response.get_fields('Set-Cookie')
+ # #=> ["session=al98axx; expires=Fri, 31-Dec-1999 23:58:23",
+ # "query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23"]
+ # p response['Set-Cookie']
+ # #=> "session=al98axx; expires=Fri, 31-Dec-1999 23:58:23, query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23"
+ #
+ def get_fields(key)
+ return nil unless @header[key.downcase]
+ @header[key.downcase].dup
+ end
+
+ # Returns the header field corresponding to the case-insensitive key.
+ # Returns the default value +args+, or the result of the block, or nil,
+ # if there's no header field named key. See Hash#fetch
+ def fetch(key, *args, &block) #:yield: +key+
+ a = @header.fetch(key.downcase, *args, &block)
+ a.join(', ')
+ end
+
+ # Iterates for each header names and values.
+ def each_header #:yield: +key+, +value+
+ @header.each do |k,va|
+ yield k, va.join(', ')
+ end
+ end
+
+ alias each each_header
+
+ # Iterates for each header names.
+ def each_name(&block) #:yield: +key+
+ @header.each_key(&block)
+ end
+
+ alias each_key each_name
+
+ # Iterates for each capitalized header names.
+ def each_capitalized_name(&block) #:yield: +key+
+ @header.each_key do |k|
+ yield capitalize(k)
+ end
+ end
+
+ # Iterates for each header values.
+ def each_value #:yield: +value+
+ @header.each_value do |va|
+ yield va.join(', ')
+ end
+ end
+
+ # Removes a header field.
+ def delete(key)
+ @header.delete(key.downcase)
+ end
+
+ # true if +key+ header exists.
+ def key?(key)
+ @header.key?(key.downcase)
+ end
+
+ # Returns a Hash consist of header names and values.
+ def to_hash
+ @header.dup
+ end
+
+ # As for #each_header, except the keys are provided in capitalized form.
+ def each_capitalized
+ @header.each do |k,v|
+ yield capitalize(k), v.join(', ')
+ end
+ end
+
+ alias canonical_each each_capitalized
+
+ def capitalize(name)
+ name.split(/-/).map {|s| s.capitalize }.join('-')
+ end
+ private :capitalize
+
+ # Returns an Array of Range objects which represents Range: header field,
+ # or +nil+ if there is no such header.
+ def range
+ return nil unless @header['range']
+ self['Range'].split(/,/).map {|spec|
+ m = /bytes\s*=\s*(\d+)?\s*-\s*(\d+)?/i.match(spec) or
+ raise HTTPHeaderSyntaxError, "wrong Range: #{spec}"
+ d1 = m[1].to_i
+ d2 = m[2].to_i
+ if m[1] and m[2] then d1..d2
+ elsif m[1] then d1..-1
+ elsif m[2] then -d2..-1
+ else
+ raise HTTPHeaderSyntaxError, 'range is not specified'
+ end
+ }
+ end
+
+ # Set Range: header from Range (arg r) or beginning index and
+ # length from it (arg idx&len).
+ #
+ # req.range = (0..1023)
+ # req.set_range 0, 1023
+ #
+ def set_range(r, e = nil)
+ unless r
+ @header.delete 'range'
+ return r
+ end
+ r = (r...r+e) if e
+ case r
+ when Numeric
+ n = r.to_i
+ rangestr = (n > 0 ? "0-#{n-1}" : "-#{-n}")
+ when Range
+ first = r.first
+ last = r.last
+ last -= 1 if r.exclude_end?
+ if last == -1
+ rangestr = (first > 0 ? "#{first}-" : "-#{-first}")
+ else
+ raise HTTPHeaderSyntaxError, 'range.first is negative' if first < 0
+ raise HTTPHeaderSyntaxError, 'range.last is negative' if last < 0
+ raise HTTPHeaderSyntaxError, 'must be .first < .last' if first > last
+ rangestr = "#{first}-#{last}"
+ end
+ else
+ raise TypeError, 'Range/Integer is required'
+ end
+ @header['range'] = ["bytes=#{rangestr}"]
+ r
+ end
+
+ alias range= set_range
+
+ # Returns an Integer object which represents the Content-Length: header field
+ # or +nil+ if that field is not provided.
+ def content_length
+ return nil unless key?('Content-Length')
+ len = self['Content-Length'].slice(/\d+/) or
+ raise HTTPHeaderSyntaxError, 'wrong Content-Length format'
+ len.to_i
+ end
+
+ def content_length=(len)
+ unless len
+ @header.delete 'content-length'
+ return nil
+ end
+ @header['content-length'] = [len.to_i.to_s]
+ end
+
+ # Returns "true" if the "transfer-encoding" header is present and
+ # set to "chunked". This is an HTTP/1.1 feature, allowing the
+ # the content to be sent in "chunks" without at the outset
+ # stating the entire content length.
+ def chunked?
+ return false unless @header['transfer-encoding']
+ field = self['Transfer-Encoding']
+ (/(?:\A|[^\-\w])chunked(?![\-\w])/i =~ field) ? true : false
+ end
+
+ # Returns a Range object which represents Content-Range: header field.
+ # This indicates, for a partial entity body, where this fragment
+ # fits inside the full entity body, as range of byte offsets.
+ def content_range
+ return nil unless @header['content-range']
+ m = %r<bytes\s+(\d+)-(\d+)/(\d+|\*)>i.match(self['Content-Range']) or
+ raise HTTPHeaderSyntaxError, 'wrong Content-Range format'
+ m[1].to_i .. m[2].to_i + 1
+ end
+
+ # The length of the range represented in Content-Range: header.
+ def range_length
+ r = content_range() or return nil
+ r.end - r.begin
+ end
+
+ # Returns a content type string such as "text/html".
+ # This method returns nil if Content-Type: header field does not exist.
+ def content_type
+ return nil unless main_type()
+ if sub_type()
+ then "#{main_type()}/#{sub_type()}"
+ else main_type()
+ end
+ end
+
+ # Returns a content type string such as "text".
+ # This method returns nil if Content-Type: header field does not exist.
+ def main_type
+ return nil unless @header['content-type']
+ self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip
+ end
+
+ # Returns a content type string such as "html".
+ # This method returns nil if Content-Type: header field does not exist
+ # or sub-type is not given (e.g. "Content-Type: text").
+ def sub_type
+ return nil unless @header['content-type']
+ main, sub = *self['Content-Type'].split(';').first.to_s.split('/')
+ return nil unless sub
+ sub.strip
+ end
+
+ # Returns content type parameters as a Hash as like
+ # {"charset" => "iso-2022-jp"}.
+ def type_params
+ result = {}
+ list = self['Content-Type'].to_s.split(';')
+ list.shift
+ list.each do |param|
+ k, v = *param.split('=', 2)
+ result[k.strip] = v.strip
+ end
+ result
+ end
+
+ # Set Content-Type: header field by +type+ and +params+.
+ # +type+ must be a String, +params+ must be a Hash.
+ def set_content_type(type, params = {})
+ @header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')]
+ end
+
+ alias content_type= set_content_type
+
+ # Set header fields and a body from HTML form data.
+ # +params+ should be a Hash containing HTML form data.
+ # Optional argument +sep+ means data record separator.
+ #
+ # This method also set Content-Type: header field to
+ # application/x-www-form-urlencoded.
+ def set_form_data(params, sep = '&')
+ self.body = params.map {|k,v| "#{urlencode(k.to_s)}=#{urlencode(v.to_s)}" }.join(sep)
+ self.content_type = 'application/x-www-form-urlencoded'
+ end
+
+ alias form_data= set_form_data
+
+ def urlencode(str)
+ str.gsub(/[^a-zA-Z0-9_\.\-]/n) {|s| sprintf('%%%02x', s[0]) }
+ end
+ private :urlencode
+
+ # Set the Authorization: header for "Basic" authorization.
+ def basic_auth(account, password)
+ @header['authorization'] = [basic_encode(account, password)]
+ end
+
+ # Set Proxy-Authorization: header for "Basic" authorization.
+ def proxy_basic_auth(account, password)
+ @header['proxy-authorization'] = [basic_encode(account, password)]
+ end
+
+ def basic_encode(account, password)
+ 'Basic ' + ["#{account}:#{password}"].pack('m').delete("\r\n")
+ end
+ private :basic_encode
+
+ end
+
+
+ #
+ # Parent of HTTPRequest class. Do not use this directly; use
+ # a subclass of HTTPRequest.
+ #
+ # Mixes in the HTTPHeader module.
+ #
+ class HTTPGenericRequest
+
+ include HTTPHeader
+
+ def initialize(m, reqbody, resbody, path, initheader = nil)
+ @method = m
+ @request_has_body = reqbody
+ @response_has_body = resbody
+ raise ArgumentError, "HTTP request path is empty" if path.empty?
+ @path = path
+ initialize_http_header initheader
+ self['Accept'] ||= '*/*'
+ @body = nil
+ @body_stream = nil
+ end
+
+ attr_reader :method
+ attr_reader :path
+
+ def inspect
+ "\#<#{self.class} #{@method}>"
+ end
+
+ def request_body_permitted?
+ @request_has_body
+ end
+
+ def response_body_permitted?
+ @response_has_body
+ end
+
+ def body_exist?
+ warn "Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?" if $VERBOSE
+ response_body_permitted?
+ end
+
+ attr_reader :body
+
+ def body=(str)
+ @body = str
+ @body_stream = nil
+ str
+ end
+
+ attr_reader :body_stream
+
+ def body_stream=(input)
+ @body = nil
+ @body_stream = input
+ input
+ end
+
+ def set_body_internal(str) #:nodoc: internal use only
+ raise ArgumentError, "both of body argument and HTTPRequest#body set" if str and (@body or @body_stream)
+ self.body = str if str
+ end
+
+ #
+ # write
+ #
+
+ def exec(sock, ver, path) #:nodoc: internal use only
+ if @body
+ send_request_with_body sock, ver, path, @body
+ elsif @body_stream
+ send_request_with_body_stream sock, ver, path, @body_stream
+ else
+ write_header sock, ver, path
+ end
+ end
+
+ private
+
+ def send_request_with_body(sock, ver, path, body)
+ self.content_length = body.length
+ delete 'Transfer-Encoding'
+ supply_default_content_type
+ write_header sock, ver, path
+ sock.write body
+ end
+
+ def send_request_with_body_stream(sock, ver, path, f)
+ unless content_length() or chunked?
+ raise ArgumentError,
+ "Content-Length not given and Transfer-Encoding is not `chunked'"
+ end
+ supply_default_content_type
+ write_header sock, ver, path
+ if chunked?
+ while s = f.read(1024)
+ sock.write(sprintf("%x\r\n", s.length) << s << "\r\n")
+ end
+ sock.write "0\r\n\r\n"
+ else
+ while s = f.read(1024)
+ sock.write s
+ end
+ end
+ end
+
+ def supply_default_content_type
+ return if content_type()
+ warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE
+ set_content_type 'application/x-www-form-urlencoded'
+ end
+
+ def write_header(sock, ver, path)
+ buf = "#{@method} #{path} HTTP/#{ver}\r\n"
+ each_capitalized do |k,v|
+ buf << "#{k}: #{v}\r\n"
+ end
+ buf << "\r\n"
+ sock.write buf
+ end
+
+ end
+
+
+ #
+ # HTTP request class. This class wraps request header and entity path.
+ # You *must* use its subclass, Net::HTTP::Get, Post, Head.
+ #
+ class HTTPRequest < HTTPGenericRequest
+
+ # Creates HTTP request object.
+ def initialize(path, initheader = nil)
+ super self.class::METHOD,
+ self.class::REQUEST_HAS_BODY,
+ self.class::RESPONSE_HAS_BODY,
+ path, initheader
+ end
+ end
+
+
+ class HTTP # reopen
+ #
+ # HTTP 1.1 methods --- RFC2616
+ #
+
+ class Get < HTTPRequest
+ METHOD = 'GET'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Head < HTTPRequest
+ METHOD = 'HEAD'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = false
+ end
+
+ class Post < HTTPRequest
+ METHOD = 'POST'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Put < HTTPRequest
+ METHOD = 'PUT'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Delete < HTTPRequest
+ METHOD = 'DELETE'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Options < HTTPRequest
+ METHOD = 'OPTIONS'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = false
+ end
+
+ class Trace < HTTPRequest
+ METHOD = 'TRACE'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+ end
+
+ #
+ # WebDAV methods --- RFC2518
+ #
+
+ class Propfind < HTTPRequest
+ METHOD = 'PROPFIND'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Proppatch < HTTPRequest
+ METHOD = 'PROPPATCH'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Mkcol < HTTPRequest
+ METHOD = 'MKCOL'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Copy < HTTPRequest
+ METHOD = 'COPY'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Move < HTTPRequest
+ METHOD = 'MOVE'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Lock < HTTPRequest
+ METHOD = 'LOCK'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Unlock < HTTPRequest
+ METHOD = 'UNLOCK'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+ end
+ end
+
+
+ ###
+ ### Response
+ ###
+
+ # HTTP exception class.
+ # You must use its subclasses.
+ module HTTPExceptions
+ def initialize(msg, res) #:nodoc:
+ super msg
+ @response = res
+ end
+ attr_reader :response
+ alias data response #:nodoc: obsolete
+ end
+ class HTTPError < ProtocolError
+ include HTTPExceptions
+ end
+ class HTTPRetriableError < ProtoRetriableError
+ include HTTPExceptions
+ end
+ class HTTPServerException < ProtoServerError
+ # We cannot use the name "HTTPServerError", it is the name of the response.
+ include HTTPExceptions
+ end
+ class HTTPFatalError < ProtoFatalError
+ include HTTPExceptions
+ end
+
+
+ # HTTP response class. This class wraps response header and entity.
+ # Mixes in the HTTPHeader module, which provides access to response
+ # header values both via hash-like methods and individual readers.
+ # Note that each possible HTTP response code defines its own
+ # HTTPResponse subclass. These are listed below.
+ # All classes are
+ # defined under the Net module. Indentation indicates inheritance.
+ #
+ # xxx HTTPResponse
+ #
+ # 1xx HTTPInformation
+ # 100 HTTPContinue
+ # 101 HTTPSwitchProtocol
+ #
+ # 2xx HTTPSuccess
+ # 200 HTTPOK
+ # 201 HTTPCreated
+ # 202 HTTPAccepted
+ # 203 HTTPNonAuthoritativeInformation
+ # 204 HTTPNoContent
+ # 205 HTTPResetContent
+ # 206 HTTPPartialContent
+ #
+ # 3xx HTTPRedirection
+ # 300 HTTPMultipleChoice
+ # 301 HTTPMovedPermanently
+ # 302 HTTPFound
+ # 303 HTTPSeeOther
+ # 304 HTTPNotModified
+ # 305 HTTPUseProxy
+ # 307 HTTPTemporaryRedirect
+ #
+ # 4xx HTTPClientError
+ # 400 HTTPBadRequest
+ # 401 HTTPUnauthorized
+ # 402 HTTPPaymentRequired
+ # 403 HTTPForbidden
+ # 404 HTTPNotFound
+ # 405 HTTPMethodNotAllowed
+ # 406 HTTPNotAcceptable
+ # 407 HTTPProxyAuthenticationRequired
+ # 408 HTTPRequestTimeOut
+ # 409 HTTPConflict
+ # 410 HTTPGone
+ # 411 HTTPLengthRequired
+ # 412 HTTPPreconditionFailed
+ # 413 HTTPRequestEntityTooLarge
+ # 414 HTTPRequestURITooLong
+ # 415 HTTPUnsupportedMediaType
+ # 416 HTTPRequestedRangeNotSatisfiable
+ # 417 HTTPExpectationFailed
+ #
+ # 5xx HTTPServerError
+ # 500 HTTPInternalServerError
+ # 501 HTTPNotImplemented
+ # 502 HTTPBadGateway
+ # 503 HTTPServiceUnavailable
+ # 504 HTTPGatewayTimeOut
+ # 505 HTTPVersionNotSupported
+ #
+ # xxx HTTPUnknownResponse
+ #
+ class HTTPResponse
+ # true if the response has body.
+ def HTTPResponse.body_permitted?
+ self::HAS_BODY
+ end
+
+ def HTTPResponse.exception_type # :nodoc: internal use only
+ self::EXCEPTION_TYPE
+ end
+ end # reopened after
+
+ # :stopdoc:
+
+ class HTTPUnknownResponse < HTTPResponse
+ HAS_BODY = true
+ EXCEPTION_TYPE = HTTPError
+ end
+ class HTTPInformation < HTTPResponse # 1xx
+ HAS_BODY = false
+ EXCEPTION_TYPE = HTTPError
+ end
+ class HTTPSuccess < HTTPResponse # 2xx
+ HAS_BODY = true
+ EXCEPTION_TYPE = HTTPError
+ end
+ class HTTPRedirection < HTTPResponse # 3xx
+ HAS_BODY = true
+ EXCEPTION_TYPE = HTTPRetriableError
+ end
+ class HTTPClientError < HTTPResponse # 4xx
+ HAS_BODY = true
+ EXCEPTION_TYPE = HTTPServerException # for backward compatibility
+ end
+ class HTTPServerError < HTTPResponse # 5xx
+ HAS_BODY = true
+ EXCEPTION_TYPE = HTTPFatalError # for backward compatibility
+ end
+
+ class HTTPContinue < HTTPInformation # 100
+ HAS_BODY = false
+ end
+ class HTTPSwitchProtocol < HTTPInformation # 101
+ HAS_BODY = false
+ end
+
+ class HTTPOK < HTTPSuccess # 200
+ HAS_BODY = true
+ end
+ class HTTPCreated < HTTPSuccess # 201
+ HAS_BODY = true
+ end
+ class HTTPAccepted < HTTPSuccess # 202
+ HAS_BODY = true
+ end
+ class HTTPNonAuthoritativeInformation < HTTPSuccess # 203
+ HAS_BODY = true
+ end
+ class HTTPNoContent < HTTPSuccess # 204
+ HAS_BODY = false
+ end
+ class HTTPResetContent < HTTPSuccess # 205
+ HAS_BODY = false
+ end
+ class HTTPPartialContent < HTTPSuccess # 206
+ HAS_BODY = true
+ end
+
+ class HTTPMultipleChoice < HTTPRedirection # 300
+ HAS_BODY = true
+ end
+ class HTTPMovedPermanently < HTTPRedirection # 301
+ HAS_BODY = true
+ end
+ class HTTPFound < HTTPRedirection # 302
+ HAS_BODY = true
+ end
+ HTTPMovedTemporarily = HTTPFound
+ class HTTPSeeOther < HTTPRedirection # 303
+ HAS_BODY = true
+ end
+ class HTTPNotModified < HTTPRedirection # 304
+ HAS_BODY = false
+ end
+ class HTTPUseProxy < HTTPRedirection # 305
+ HAS_BODY = false
+ end
+ # 306 unused
+ class HTTPTemporaryRedirect < HTTPRedirection # 307
+ HAS_BODY = true
+ end
+
+ class HTTPBadRequest < HTTPClientError # 400
+ HAS_BODY = true
+ end
+ class HTTPUnauthorized < HTTPClientError # 401
+ HAS_BODY = true
+ end
+ class HTTPPaymentRequired < HTTPClientError # 402
+ HAS_BODY = true
+ end
+ class HTTPForbidden < HTTPClientError # 403
+ HAS_BODY = true
+ end
+ class HTTPNotFound < HTTPClientError # 404
+ HAS_BODY = true
+ end
+ class HTTPMethodNotAllowed < HTTPClientError # 405
+ HAS_BODY = true
+ end
+ class HTTPNotAcceptable < HTTPClientError # 406
+ HAS_BODY = true
+ end
+ class HTTPProxyAuthenticationRequired < HTTPClientError # 407
+ HAS_BODY = true
+ end
+ class HTTPRequestTimeOut < HTTPClientError # 408
+ HAS_BODY = true
+ end
+ class HTTPConflict < HTTPClientError # 409
+ HAS_BODY = true
+ end
+ class HTTPGone < HTTPClientError # 410
+ HAS_BODY = true
+ end
+ class HTTPLengthRequired < HTTPClientError # 411
+ HAS_BODY = true
+ end
+ class HTTPPreconditionFailed < HTTPClientError # 412
+ HAS_BODY = true
+ end
+ class HTTPRequestEntityTooLarge < HTTPClientError # 413
+ HAS_BODY = true
+ end
+ class HTTPRequestURITooLong < HTTPClientError # 414
+ HAS_BODY = true
+ end
+ HTTPRequestURITooLarge = HTTPRequestURITooLong
+ class HTTPUnsupportedMediaType < HTTPClientError # 415
+ HAS_BODY = true
+ end
+ class HTTPRequestedRangeNotSatisfiable < HTTPClientError # 416
+ HAS_BODY = true
+ end
+ class HTTPExpectationFailed < HTTPClientError # 417
+ HAS_BODY = true
+ end
+
+ class HTTPInternalServerError < HTTPServerError # 500
+ HAS_BODY = true
+ end
+ class HTTPNotImplemented < HTTPServerError # 501
+ HAS_BODY = true
+ end
+ class HTTPBadGateway < HTTPServerError # 502
+ HAS_BODY = true
+ end
+ class HTTPServiceUnavailable < HTTPServerError # 503
+ HAS_BODY = true
+ end
+ class HTTPGatewayTimeOut < HTTPServerError # 504
+ HAS_BODY = true
+ end
+ class HTTPVersionNotSupported < HTTPServerError # 505
+ HAS_BODY = true
+ end
+
+ # :startdoc:
+
+
+ class HTTPResponse # reopen
+
+ CODE_CLASS_TO_OBJ = {
+ '1' => HTTPInformation,
+ '2' => HTTPSuccess,
+ '3' => HTTPRedirection,
+ '4' => HTTPClientError,
+ '5' => HTTPServerError
+ }
+ CODE_TO_OBJ = {
+ '100' => HTTPContinue,
+ '101' => HTTPSwitchProtocol,
+
+ '200' => HTTPOK,
+ '201' => HTTPCreated,
+ '202' => HTTPAccepted,
+ '203' => HTTPNonAuthoritativeInformation,
+ '204' => HTTPNoContent,
+ '205' => HTTPResetContent,
+ '206' => HTTPPartialContent,
+
+ '300' => HTTPMultipleChoice,
+ '301' => HTTPMovedPermanently,
+ '302' => HTTPFound,
+ '303' => HTTPSeeOther,
+ '304' => HTTPNotModified,
+ '305' => HTTPUseProxy,
+ '307' => HTTPTemporaryRedirect,
+
+ '400' => HTTPBadRequest,
+ '401' => HTTPUnauthorized,
+ '402' => HTTPPaymentRequired,
+ '403' => HTTPForbidden,
+ '404' => HTTPNotFound,
+ '405' => HTTPMethodNotAllowed,
+ '406' => HTTPNotAcceptable,
+ '407' => HTTPProxyAuthenticationRequired,
+ '408' => HTTPRequestTimeOut,
+ '409' => HTTPConflict,
+ '410' => HTTPGone,
+ '411' => HTTPLengthRequired,
+ '412' => HTTPPreconditionFailed,
+ '413' => HTTPRequestEntityTooLarge,
+ '414' => HTTPRequestURITooLong,
+ '415' => HTTPUnsupportedMediaType,
+ '416' => HTTPRequestedRangeNotSatisfiable,
+ '417' => HTTPExpectationFailed,
+
+ '500' => HTTPInternalServerError,
+ '501' => HTTPNotImplemented,
+ '502' => HTTPBadGateway,
+ '503' => HTTPServiceUnavailable,
+ '504' => HTTPGatewayTimeOut,
+ '505' => HTTPVersionNotSupported
+ }
+
+ class << HTTPResponse
+ def read_new(sock) #:nodoc: internal use only
+ httpv, code, msg = read_status_line(sock)
+ res = response_class(code).new(httpv, code, msg)
+ each_response_header(sock) do |k,v|
+ res.add_field k, v
+ end
+ res
+ end
+
+ private
+
+ def read_status_line(sock)
+ str = sock.readline
+ m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/in.match(str) or
+ raise HTTPBadResponse, "wrong status line: #{str.dump}"
+ m.captures
+ end
+
+ def response_class(code)
+ CODE_TO_OBJ[code] or
+ CODE_CLASS_TO_OBJ[code[0,1]] or
+ HTTPUnknownResponse
+ end
+
+ def each_response_header(sock)
+ while true
+ line = sock.readuntil("\n", true).sub(/\s+\z/, '')
+ break if line.empty?
+ m = /\A([^:]+):\s*/.match(line) or
+ raise HTTPBadResponse, 'wrong header line format'
+ yield m[1], m.post_match
+ end
+ end
+ end
+
+ # next is to fix bug in RDoc, where the private inside class << self
+ # spills out.
+ public
+
+ include HTTPHeader
+
+ def initialize(httpv, code, msg) #:nodoc: internal use only
+ @http_version = httpv
+ @code = code
+ @message = msg
+ initialize_http_header nil
+ @body = nil
+ @read = false
+ end
+
+ # The HTTP version supported by the server.
+ attr_reader :http_version
+
+ # HTTP result code string. For example, '302'. You can also
+ # determine the response type by which response subclass the
+ # response object is an instance of.
+ attr_reader :code
+
+ # HTTP result message. For example, 'Not Found'.
+ attr_reader :message
+ alias msg message # :nodoc: obsolete
+
+ def inspect
+ "#<#{self.class} #{@code} #{@message} readbody=#{@read}>"
+ end
+
+ # For backward compatibility.
+ # To allow Net::HTTP 1.1 style assignment
+ # e.g.
+ # response, body = Net::HTTP.get(....)
+ #
+ def to_ary
+ warn "net/http.rb: warning: Net::HTTP v1.1 style assignment found at #{caller(1)[0]}; use `response = http.get(...)' instead." if $VERBOSE
+ res = self.dup
+ class << res
+ undef to_ary
+ end
+ [res, res.body]
+ end
+
+ #
+ # response <-> exception relationship
+ #
+
+ def code_type #:nodoc:
+ self.class
+ end
+
+ def error! #:nodoc:
+ raise error_type().new(@code + ' ' + @message.dump, self)
+ end
+
+ def error_type #:nodoc:
+ self.class::EXCEPTION_TYPE
+ end
+
+ # Raises HTTP error if the response is not 2xx.
+ def value
+ error! unless self.kind_of?(HTTPSuccess)
+ end
+
+ #
+ # header (for backward compatibility only; DO NOT USE)
+ #
+
+ def response #:nodoc:
+ warn "#{caller(1)[0]}: warning: HTTPResponse#response is obsolete" if $VERBOSE
+ self
+ end
+
+ def header #:nodoc:
+ warn "#{caller(1)[0]}: warning: HTTPResponse#header is obsolete" if $VERBOSE
+ self
+ end
+
+ def read_header #:nodoc:
+ warn "#{caller(1)[0]}: warning: HTTPResponse#read_header is obsolete" if $VERBOSE
+ self
+ end
+
+ #
+ # body
+ #
+
+ def reading_body(sock, reqmethodallowbody) #:nodoc: internal use only
+ @socket = sock
+ @body_exist = reqmethodallowbody && self.class.body_permitted?
+ begin
+ yield
+ self.body # ensure to read body
+ ensure
+ @socket = nil
+ end
+ end
+
+ # Gets entity body. If the block given, yields it to +block+.
+ # The body is provided in fragments, as it is read in from the socket.
+ #
+ # Calling this method a second or subsequent time will return the
+ # already read string.
+ #
+ # http.request_get('/index.html') {|res|
+ # puts res.read_body
+ # }
+ #
+ # http.request_get('/index.html') {|res|
+ # p res.read_body.object_id # 538149362
+ # p res.read_body.object_id # 538149362
+ # }
+ #
+ # # using iterator
+ # http.request_get('/index.html') {|res|
+ # res.read_body do |segment|
+ # print segment
+ # end
+ # }
+ #
+ def read_body(dest = nil, &block)
+ if @read
+ raise IOError, "#{self.class}\#read_body called twice" if dest or block
+ return @body
+ end
+ to = procdest(dest, block)
+ stream_check
+ if @body_exist
+ read_body_0 to
+ @body = to
+ else
+ @body = nil
+ end
+ @read = true
+
+ @body
+ end
+
+ # Returns the entity body.
+ #
+ # Calling this method a second or subsequent time will return the
+ # already read string.
+ #
+ # http.request_get('/index.html') {|res|
+ # puts res.body
+ # }
+ #
+ # http.request_get('/index.html') {|res|
+ # p res.body.object_id # 538149362
+ # p res.body.object_id # 538149362
+ # }
+ #
+ def body
+ read_body()
+ end
+
+ alias entity body #:nodoc: obsolete
+
+ private
+
+ def read_body_0(dest)
+ if chunked?
+ read_chunked dest
+ return
+ end
+ clen = content_length()
+ if clen
+ @socket.read clen, dest, true # ignore EOF
+ return
+ end
+ clen = range_length()
+ if clen
+ @socket.read clen, dest
+ return
+ end
+ @socket.read_all dest
+ end
+
+ def read_chunked(dest)
+ len = nil
+ total = 0
+ while true
+ line = @socket.readline
+ hexlen = line.slice(/[0-9a-fA-F]+/) or
+ raise HTTPBadResponse, "wrong chunk size line: #{line}"
+ len = hexlen.hex
+ break if len == 0
+ @socket.read len, dest; total += len
+ @socket.read 2 # \r\n
+ end
+ until @socket.readline.empty?
+ # none
+ end
+ end
+
+ def stream_check
+ raise IOError, 'attempt to read body out of block' if @socket.closed?
+ end
+
+ def procdest(dest, block)
+ raise ArgumentError, 'both arg and block given for HTTP method' \
+ if dest and block
+ if block
+ ReadAdapter.new(block)
+ else
+ dest || ''
+ end
+ end
+
+ end
+
+
+ # :enddoc:
+
+ #--
+ # for backward compatibility
+ class HTTP
+ ProxyMod = ProxyDelta
+ end
+ module NetPrivate
+ HTTPRequest = ::Net::HTTPRequest
+ end
+
+ HTTPInformationCode = HTTPInformation
+ HTTPSuccessCode = HTTPSuccess
+ HTTPRedirectionCode = HTTPRedirection
+ HTTPRetriableCode = HTTPRedirection
+ HTTPClientErrorCode = HTTPClientError
+ HTTPFatalErrorCode = HTTPClientError
+ HTTPServerErrorCode = HTTPServerError
+ HTTPResponceReceiver = HTTPResponse
+
+end # module Net
diff --git a/ruby_1_8_6/lib/net/https.rb b/ruby_1_8_6/lib/net/https.rb
new file mode 100644
index 0000000000..e296dbbed4
--- /dev/null
+++ b/ruby_1_8_6/lib/net/https.rb
@@ -0,0 +1,173 @@
+=begin
+
+= $RCSfile$ -- SSL/TLS enhancement for Net::HTTP.
+
+== Info
+ 'OpenSSL for Ruby 2' project
+ Copyright (C) 2001 GOTOU Yuuzou <gotoyuzo@notwork.org>
+ All rights reserved.
+
+== Licence
+ This program is licenced under the same licence as Ruby.
+ (See the file 'LICENCE'.)
+
+== Requirements
+ This program requires Net 1.2.0 or higher version.
+ You can get it from RAA or Ruby's CVS repository.
+
+== Version
+ $Id$
+
+ 2001-11-06: Contiributed to Ruby/OpenSSL project.
+ 2004-03-06: Some code is merged in to net/http.
+
+== Example
+
+Here is a simple HTTP client:
+
+ require 'net/http'
+ require 'uri'
+
+ uri = URI.parse(ARGV[0] || 'http://localhost/')
+ http = Net::HTTP.new(uri.host, uri.port)
+ http.start {
+ http.request_get(uri.path) {|res|
+ print res.body
+ }
+ }
+
+It can be replaced by the following code:
+
+ require 'net/https'
+ require 'uri'
+
+ uri = URI.parse(ARGV[0] || 'https://localhost/')
+ http = Net::HTTP.new(uri.host, uri.port)
+ http.use_ssl = true if uri.scheme == "https" # enable SSL/TLS
+ http.start {
+ http.request_get(uri.path) {|res|
+ print res.body
+ }
+ }
+
+== class Net::HTTP
+
+=== Instance Methods
+
+: use_ssl?
+ returns true if use SSL/TLS with HTTP.
+
+: use_ssl=((|true_or_false|))
+ sets use_ssl.
+
+: peer_cert
+ return the X.509 certificates the server presented.
+
+: key, key=((|key|))
+ Sets an OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
+ (This method is appeared in Michal Rokos's OpenSSL extention.)
+
+: cert, cert=((|cert|))
+ Sets an OpenSSL::X509::Certificate object as client certificate
+ (This method is appeared in Michal Rokos's OpenSSL extention).
+
+: ca_file, ca_file=((|path|))
+ Sets path of a CA certification file in PEM format.
+ The file can contrain several CA certificats.
+
+: ca_path, ca_path=((|path|))
+ Sets path of a CA certification directory containing certifications
+ in PEM format.
+
+: verify_mode, verify_mode=((|mode|))
+ Sets the flags for server the certification verification at
+ begining of SSL/TLS session.
+ OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable.
+
+: verify_callback, verify_callback=((|proc|))
+ Sets the verify callback for the server certification verification.
+
+: verify_depth, verify_depth=((|num|))
+ Sets the maximum depth for the certificate chain verification.
+
+: cert_store, cert_store=((|store|))
+ Sets the X509::Store to verify peer certificate.
+
+: ssl_timeout, ssl_timeout=((|sec|))
+ Sets the SSL timeout seconds.
+
+=end
+
+require 'net/http'
+require 'openssl'
+
+module Net
+
+ class HTTP
+ remove_method :use_ssl?
+ def use_ssl?
+ @use_ssl
+ end
+
+ # For backward compatibility.
+ alias use_ssl use_ssl?
+
+ # Turn on/off SSL.
+ # This flag must be set before starting session.
+ # If you change use_ssl value after session started,
+ # a Net::HTTP object raises IOError.
+ def use_ssl=(flag)
+ flag = (flag ? true : false)
+ raise IOError, "use_ssl value changed, but session already started" \
+ if started? and @use_ssl != flag
+ if flag and not @ssl_context
+ @ssl_context = OpenSSL::SSL::SSLContext.new
+ end
+ @use_ssl = flag
+ end
+
+ def self.ssl_context_accessor(name)
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
+ def #{name}
+ return nil unless @ssl_context
+ @ssl_context.#{name}
+ end
+
+ def #{name}=(val)
+ @ssl_context ||= OpenSSL::SSL::SSLContext.new
+ @ssl_context.#{name} = val
+ end
+ End
+ end
+
+ ssl_context_accessor :key
+ ssl_context_accessor :cert
+ ssl_context_accessor :ca_file
+ ssl_context_accessor :ca_path
+ ssl_context_accessor :verify_mode
+ ssl_context_accessor :verify_callback
+ ssl_context_accessor :verify_depth
+ ssl_context_accessor :cert_store
+
+ def ssl_timeout
+ return nil unless @ssl_context
+ @ssl_context.timeout
+ end
+
+ def ssl_timeout=(sec)
+ raise ArgumentError, 'Net::HTTP#ssl_timeout= called but use_ssl=false' \
+ unless use_ssl?
+ @ssl_context ||= OpenSSL::SSL::SSLContext.new
+ @ssl_context.timeout = sec
+ end
+
+ # For backward compatibility
+ alias timeout= ssl_timeout=
+
+ def peer_cert
+ return nil if not use_ssl? or not @socket
+ @socket.io.peer_cert
+ end
+ end
+
+end
diff --git a/ruby_1_8_6/lib/net/imap.rb b/ruby_1_8_6/lib/net/imap.rb
new file mode 100644
index 0000000000..6436940df6
--- /dev/null
+++ b/ruby_1_8_6/lib/net/imap.rb
@@ -0,0 +1,3370 @@
+#
+# = net/imap.rb
+#
+# Copyright (C) 2000 Shugo Maeda <shugo@ruby-lang.org>
+#
+# This library is distributed under the terms of the Ruby license.
+# You can freely distribute/modify this library.
+#
+# Documentation: Shugo Maeda, with RDoc conversion and overview by William
+# Webber.
+#
+# See Net::IMAP for documentation.
+#
+
+
+require "socket"
+require "monitor"
+require "digest/md5"
+begin
+ require "openssl"
+rescue LoadError
+end
+
+module Net
+
+ #
+ # Net::IMAP implements Internet Message Access Protocol (IMAP) client
+ # functionality. The protocol is described in [IMAP].
+ #
+ # == IMAP Overview
+ #
+ # An IMAP client connects to a server, and then authenticates
+ # itself using either #authenticate() or #login(). Having
+ # authenticated itself, there is a range of commands
+ # available to it. Most work with mailboxes, which may be
+ # arranged in an hierarchical namespace, and each of which
+ # contains zero or more messages. How this is implemented on
+ # the server is implementation-dependent; on a UNIX server, it
+ # will frequently be implemented as a files in mailbox format
+ # within a hierarchy of directories.
+ #
+ # To work on the messages within a mailbox, the client must
+ # first select that mailbox, using either #select() or (for
+ # read-only access) #examine(). Once the client has successfully
+ # selected a mailbox, they enter _selected_ state, and that
+ # mailbox becomes the _current_ mailbox, on which mail-item
+ # related commands implicitly operate.
+ #
+ # Messages have two sorts of identifiers: message sequence
+ # numbers, and UIDs.
+ #
+ # Message sequence numbers number messages within a mail box
+ # from 1 up to the number of items in the mail box. If new
+ # message arrives during a session, it receives a sequence
+ # number equal to the new size of the mail box. If messages
+ # are expunged from the mailbox, remaining messages have their
+ # sequence numbers "shuffled down" to fill the gaps.
+ #
+ # UIDs, on the other hand, are permanently guaranteed not to
+ # identify another message within the same mailbox, even if
+ # the existing message is deleted. UIDs are required to
+ # be assigned in ascending (but not necessarily sequential)
+ # order within a mailbox; this means that if a non-IMAP client
+ # rearranges the order of mailitems within a mailbox, the
+ # UIDs have to be reassigned. An IMAP client cannot thus
+ # rearrange message orders.
+ #
+ # == Examples of Usage
+ #
+ # === List sender and subject of all recent messages in the default mailbox
+ #
+ # imap = Net::IMAP.new('mail.example.com')
+ # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
+ # imap.examine('INBOX')
+ # imap.search(["RECENT"]).each do |message_id|
+ # envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
+ # puts "#{envelope.from[0].name}: \t#{envelope.subject}"
+ # end
+ #
+ # === Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
+ #
+ # imap = Net::IMAP.new('mail.example.com')
+ # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
+ # imap.select('Mail/sent-mail')
+ # if not imap.list('Mail/', 'sent-apr03')
+ # imap.create('Mail/sent-apr03')
+ # end
+ # imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id|
+ # imap.copy(message_id, "Mail/sent-apr03")
+ # imap.store(message_id, "+FLAGS", [:Deleted])
+ # end
+ # imap.expunge
+ #
+ # == Thread Safety
+ #
+ # Net::IMAP supports concurrent threads. For example,
+ #
+ # imap = Net::IMAP.new("imap.foo.net", "imap2")
+ # imap.authenticate("cram-md5", "bar", "password")
+ # imap.select("inbox")
+ # fetch_thread = Thread.start { imap.fetch(1..-1, "UID") }
+ # search_result = imap.search(["BODY", "hello"])
+ # fetch_result = fetch_thread.value
+ # imap.disconnect
+ #
+ # This script invokes the FETCH command and the SEARCH command concurrently.
+ #
+ # == Errors
+ #
+ # An IMAP server can send three different types of responses to indicate
+ # failure:
+ #
+ # NO:: the attempted command could not be successfully completed. For
+ # instance, the username/password used for logging in are incorrect;
+ # the selected mailbox does not exists; etc.
+ #
+ # BAD:: the request from the client does not follow the server's
+ # understanding of the IMAP protocol. This includes attempting
+ # commands from the wrong client state; for instance, attempting
+ # to perform a SEARCH command without having SELECTed a current
+ # mailbox. It can also signal an internal server
+ # failure (such as a disk crash) has occurred.
+ #
+ # BYE:: the server is saying goodbye. This can be part of a normal
+ # logout sequence, and can be used as part of a login sequence
+ # to indicate that the server is (for some reason) unwilling
+ # to accept our connection. As a response to any other command,
+ # it indicates either that the server is shutting down, or that
+ # the server is timing out the client connection due to inactivity.
+ #
+ # These three error response are represented by the errors
+ # Net::IMAP::NoResponseError, Net::IMAP::BadResponseError, and
+ # Net::IMAP::ByeResponseError, all of which are subclasses of
+ # Net::IMAP::ResponseError. Essentially, all methods that involve
+ # sending a request to the server can generate one of these errors.
+ # Only the most pertinent instances have been documented below.
+ #
+ # Because the IMAP class uses Sockets for communication, its methods
+ # are also susceptible to the various errors that can occur when
+ # working with sockets. These are generally represented as
+ # Errno errors. For instance, any method that involves sending a
+ # request to the server and/or receiving a response from it could
+ # raise an Errno::EPIPE error if the network connection unexpectedly
+ # goes down. See the socket(7), ip(7), tcp(7), socket(2), connect(2),
+ # and associated man pages.
+ #
+ # Finally, a Net::IMAP::DataFormatError is thrown if low-level data
+ # is found to be in an incorrect format (for instance, when converting
+ # between UTF-8 and UTF-16), and Net::IMAP::ResponseParseError is
+ # thrown if a server response is non-parseable.
+ #
+ #
+ # == References
+ #
+ # [[IMAP]]
+ # M. Crispin, "INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1",
+ # RFC 2060, December 1996. (Note: since obsoleted by RFC 3501)
+ #
+ # [[LANGUAGE-TAGS]]
+ # Alvestrand, H., "Tags for the Identification of
+ # Languages", RFC 1766, March 1995.
+ #
+ # [[MD5]]
+ # Myers, J., and M. Rose, "The Content-MD5 Header Field", RFC
+ # 1864, October 1995.
+ #
+ # [[MIME-IMB]]
+ # Freed, N., and N. Borenstein, "MIME (Multipurpose Internet
+ # Mail Extensions) Part One: Format of Internet Message Bodies", RFC
+ # 2045, November 1996.
+ #
+ # [[RFC-822]]
+ # Crocker, D., "Standard for the Format of ARPA Internet Text
+ # Messages", STD 11, RFC 822, University of Delaware, August 1982.
+ #
+ # [[RFC-2087]]
+ # Myers, J., "IMAP4 QUOTA extension", RFC 2087, January 1997.
+ #
+ # [[RFC-2086]]
+ # Myers, J., "IMAP4 ACL extension", RFC 2086, January 1997.
+ #
+ # [[RFC-2195]]
+ # Klensin, J., Catoe, R., and Krumviede, P., "IMAP/POP AUTHorize Extension
+ # for Simple Challenge/Response", RFC 2195, September 1997.
+ #
+ # [[SORT-THREAD-EXT]]
+ # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - SORT and THREAD
+ # Extensions", draft-ietf-imapext-sort, May 2003.
+ #
+ # [[OSSL]]
+ # http://www.openssl.org
+ #
+ # [[RSSL]]
+ # http://savannah.gnu.org/projects/rubypki
+ #
+ # [[UTF7]]
+ # Goldsmith, D. and Davis, M., "UTF-7: A Mail-Safe Transformation Format of
+ # Unicode", RFC 2152, May 1997.
+ #
+ class IMAP
+ include MonitorMixin
+ if defined?(OpenSSL)
+ include OpenSSL
+ include SSL
+ end
+
+ # Returns an initial greeting response from the server.
+ attr_reader :greeting
+
+ # Returns recorded untagged responses. For example:
+ #
+ # imap.select("inbox")
+ # p imap.responses["EXISTS"][-1]
+ # #=> 2
+ # p imap.responses["UIDVALIDITY"][-1]
+ # #=> 968263756
+ attr_reader :responses
+
+ # Returns all response handlers.
+ attr_reader :response_handlers
+
+ # The thread to receive exceptions.
+ attr_accessor :client_thread
+
+ # Flag indicating a message has been seen
+ SEEN = :Seen
+
+ # Flag indicating a message has been answered
+ ANSWERED = :Answered
+
+ # Flag indicating a message has been flagged for special or urgent
+ # attention
+ FLAGGED = :Flagged
+
+ # Flag indicating a message has been marked for deletion. This
+ # will occur when the mailbox is closed or expunged.
+ DELETED = :Deleted
+
+ # Flag indicating a message is only a draft or work-in-progress version.
+ DRAFT = :Draft
+
+ # Flag indicating that the message is "recent", meaning that this
+ # session is the first session in which the client has been notified
+ # of this message.
+ RECENT = :Recent
+
+ # Flag indicating that a mailbox context name cannot contain
+ # children.
+ NOINFERIORS = :Noinferiors
+
+ # Flag indicating that a mailbox is not selected.
+ NOSELECT = :Noselect
+
+ # Flag indicating that a mailbox has been marked "interesting" by
+ # the server; this commonly indicates that the mailbox contains
+ # new messages.
+ MARKED = :Marked
+
+ # Flag indicating that the mailbox does not contains new messages.
+ UNMARKED = :Unmarked
+
+ # Returns the debug mode.
+ def self.debug
+ return @@debug
+ end
+
+ # Sets the debug mode.
+ def self.debug=(val)
+ return @@debug = val
+ end
+
+ # Adds an authenticator for Net::IMAP#authenticate. +auth_type+
+ # is the type of authentication this authenticator supports
+ # (for instance, "LOGIN"). The +authenticator+ is an object
+ # which defines a process() method to handle authentication with
+ # the server. See Net::IMAP::LoginAuthenticator and
+ # Net::IMAP::CramMD5Authenticator for examples.
+ #
+ # If +auth_type+ refers to an existing authenticator, it will be
+ # replaced by the new one.
+ def self.add_authenticator(auth_type, authenticator)
+ @@authenticators[auth_type] = authenticator
+ end
+
+ # Disconnects from the server.
+ def disconnect
+ if SSL::SSLSocket === @sock
+ @sock.io.shutdown
+ else
+ @sock.shutdown
+ end
+ @receiver_thread.join
+ @sock.close
+ end
+
+ # Returns true if disconnected from the server.
+ def disconnected?
+ return @sock.closed?
+ end
+
+ # Sends a CAPABILITY command, and returns an array of
+ # capabilities that the server supports. Each capability
+ # is a string. See [IMAP] for a list of possible
+ # capabilities.
+ #
+ # Note that the Net::IMAP class does not modify its
+ # behaviour according to the capabilities of the server;
+ # it is up to the user of the class to ensure that
+ # a certain capability is supported by a server before
+ # using it.
+ def capability
+ synchronize do
+ send_command("CAPABILITY")
+ return @responses.delete("CAPABILITY")[-1]
+ end
+ end
+
+ # Sends a NOOP command to the server. It does nothing.
+ def noop
+ send_command("NOOP")
+ end
+
+ # Sends a LOGOUT command to inform the server that the client is
+ # done with the connection.
+ def logout
+ send_command("LOGOUT")
+ end
+
+ # Sends an AUTHENTICATE command to authenticate the client.
+ # The +auth_type+ parameter is a string that represents
+ # the authentication mechanism to be used. Currently Net::IMAP
+ # supports authentication mechanisms:
+ #
+ # LOGIN:: login using cleartext user and password.
+ # CRAM-MD5:: login with cleartext user and encrypted password
+ # (see [RFC-2195] for a full description). This
+ # mechanism requires that the server have the user's
+ # password stored in clear-text password.
+ #
+ # For both these mechanisms, there should be two +args+: username
+ # and (cleartext) password. A server may not support one or other
+ # of these mechanisms; check #capability() for a capability of
+ # the form "AUTH=LOGIN" or "AUTH=CRAM-MD5".
+ #
+ # Authentication is done using the appropriate authenticator object:
+ # see @@authenticators for more information on plugging in your own
+ # authenticator.
+ #
+ # For example:
+ #
+ # imap.authenticate('LOGIN', user, password)
+ #
+ # A Net::IMAP::NoResponseError is raised if authentication fails.
+ def authenticate(auth_type, *args)
+ auth_type = auth_type.upcase
+ unless @@authenticators.has_key?(auth_type)
+ raise ArgumentError,
+ format('unknown auth type - "%s"', auth_type)
+ end
+ authenticator = @@authenticators[auth_type].new(*args)
+ send_command("AUTHENTICATE", auth_type) do |resp|
+ if resp.instance_of?(ContinuationRequest)
+ data = authenticator.process(resp.data.text.unpack("m")[0])
+ s = [data].pack("m").gsub(/\n/, "")
+ send_string_data(s)
+ put_string(CRLF)
+ end
+ end
+ end
+
+ # Sends a LOGIN command to identify the client and carries
+ # the plaintext +password+ authenticating this +user+. Note
+ # that, unlike calling #authenticate() with an +auth_type+
+ # of "LOGIN", #login() does *not* use the login authenticator.
+ #
+ # A Net::IMAP::NoResponseError is raised if authentication fails.
+ def login(user, password)
+ send_command("LOGIN", user, password)
+ end
+
+ # Sends a SELECT command to select a +mailbox+ so that messages
+ # in the +mailbox+ can be accessed.
+ #
+ # After you have selected a mailbox, you may retrieve the
+ # number of items in that mailbox from @responses["EXISTS"][-1],
+ # and the number of recent messages from @responses["RECENT"][-1].
+ # Note that these values can change if new messages arrive
+ # during a session; see #add_response_handler() for a way of
+ # detecting this event.
+ #
+ # A Net::IMAP::NoResponseError is raised if the mailbox does not
+ # exist or is for some reason non-selectable.
+ def select(mailbox)
+ synchronize do
+ @responses.clear
+ send_command("SELECT", mailbox)
+ end
+ end
+
+ # Sends a EXAMINE command to select a +mailbox+ so that messages
+ # in the +mailbox+ can be accessed. Behaves the same as #select(),
+ # except that the selected +mailbox+ is identified as read-only.
+ #
+ # A Net::IMAP::NoResponseError is raised if the mailbox does not
+ # exist or is for some reason non-examinable.
+ def examine(mailbox)
+ synchronize do
+ @responses.clear
+ send_command("EXAMINE", mailbox)
+ end
+ end
+
+ # Sends a CREATE command to create a new +mailbox+.
+ #
+ # A Net::IMAP::NoResponseError is raised if a mailbox with that name
+ # cannot be created.
+ def create(mailbox)
+ send_command("CREATE", mailbox)
+ end
+
+ # Sends a DELETE command to remove the +mailbox+.
+ #
+ # A Net::IMAP::NoResponseError is raised if a mailbox with that name
+ # cannot be deleted, either because it does not exist or because the
+ # client does not have permission to delete it.
+ def delete(mailbox)
+ send_command("DELETE", mailbox)
+ end
+
+ # Sends a RENAME command to change the name of the +mailbox+ to
+ # +newname+.
+ #
+ # A Net::IMAP::NoResponseError is raised if a mailbox with the
+ # name +mailbox+ cannot be renamed to +newname+ for whatever
+ # reason; for instance, because +mailbox+ does not exist, or
+ # because there is already a mailbox with the name +newname+.
+ def rename(mailbox, newname)
+ send_command("RENAME", mailbox, newname)
+ end
+
+ # Sends a SUBSCRIBE command to add the specified +mailbox+ name to
+ # the server's set of "active" or "subscribed" mailboxes as returned
+ # by #lsub().
+ #
+ # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
+ # subscribed to, for instance because it does not exist.
+ def subscribe(mailbox)
+ send_command("SUBSCRIBE", mailbox)
+ end
+
+ # Sends a UNSUBSCRIBE command to remove the specified +mailbox+ name
+ # from the server's set of "active" or "subscribed" mailboxes.
+ #
+ # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
+ # unsubscribed from, for instance because the client is not currently
+ # subscribed to it.
+ def unsubscribe(mailbox)
+ send_command("UNSUBSCRIBE", mailbox)
+ end
+
+ # Sends a LIST command, and returns a subset of names from
+ # the complete set of all names available to the client.
+ # +refname+ provides a context (for instance, a base directory
+ # in a directory-based mailbox hierarchy). +mailbox+ specifies
+ # a mailbox or (via wildcards) mailboxes under that context.
+ # Two wildcards may be used in +mailbox+: '*', which matches
+ # all characters *including* the hierarchy delimiter (for instance,
+ # '/' on a UNIX-hosted directory-based mailbox hierarchy); and '%',
+ # which matches all characters *except* the hierarchy delimiter.
+ #
+ # If +refname+ is empty, +mailbox+ is used directly to determine
+ # which mailboxes to match. If +mailbox+ is empty, the root
+ # name of +refname+ and the hierarchy delimiter are returned.
+ #
+ # The return value is an array of +Net::IMAP::MailboxList+. For example:
+ #
+ # imap.create("foo/bar")
+ # imap.create("foo/baz")
+ # p imap.list("", "foo/%")
+ # #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, \\
+ # #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, \\
+ # #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">]
+ def list(refname, mailbox)
+ synchronize do
+ send_command("LIST", refname, mailbox)
+ return @responses.delete("LIST")
+ end
+ end
+
+ # Sends the GETQUOTAROOT command along with specified +mailbox+.
+ # This command is generally available to both admin and user.
+ # If mailbox exists, returns an array containing objects of
+ # Net::IMAP::MailboxQuotaRoot and Net::IMAP::MailboxQuota.
+ def getquotaroot(mailbox)
+ synchronize do
+ send_command("GETQUOTAROOT", mailbox)
+ result = []
+ result.concat(@responses.delete("QUOTAROOT"))
+ result.concat(@responses.delete("QUOTA"))
+ return result
+ end
+ end
+
+ # Sends the GETQUOTA command along with specified +mailbox+.
+ # If this mailbox exists, then an array containing a
+ # Net::IMAP::MailboxQuota object is returned. This
+ # command generally is only available to server admin.
+ def getquota(mailbox)
+ synchronize do
+ send_command("GETQUOTA", mailbox)
+ return @responses.delete("QUOTA")
+ end
+ end
+
+ # Sends a SETQUOTA command along with the specified +mailbox+ and
+ # +quota+. If +quota+ is nil, then quota will be unset for that
+ # mailbox. Typically one needs to be logged in as server admin
+ # for this to work. The IMAP quota commands are described in
+ # [RFC-2087].
+ def setquota(mailbox, quota)
+ if quota.nil?
+ data = '()'
+ else
+ data = '(STORAGE ' + quota.to_s + ')'
+ end
+ send_command("SETQUOTA", mailbox, RawData.new(data))
+ end
+
+ # Sends the SETACL command along with +mailbox+, +user+ and the
+ # +rights+ that user is to have on that mailbox. If +rights+ is nil,
+ # then that user will be stripped of any rights to that mailbox.
+ # The IMAP ACL commands are described in [RFC-2086].
+ def setacl(mailbox, user, rights)
+ if rights.nil?
+ send_command("SETACL", mailbox, user, "")
+ else
+ send_command("SETACL", mailbox, user, rights)
+ end
+ end
+
+ # Send the GETACL command along with specified +mailbox+.
+ # If this mailbox exists, an array containing objects of
+ # Net::IMAP::MailboxACLItem will be returned.
+ def getacl(mailbox)
+ synchronize do
+ send_command("GETACL", mailbox)
+ return @responses.delete("ACL")[-1]
+ end
+ end
+
+ # Sends a LSUB command, and returns a subset of names from the set
+ # of names that the user has declared as being "active" or
+ # "subscribed". +refname+ and +mailbox+ are interpreted as
+ # for #list().
+ # The return value is an array of +Net::IMAP::MailboxList+.
+ def lsub(refname, mailbox)
+ synchronize do
+ send_command("LSUB", refname, mailbox)
+ return @responses.delete("LSUB")
+ end
+ end
+
+ # Sends a STATUS command, and returns the status of the indicated
+ # +mailbox+. +attr+ is a list of one or more attributes that
+ # we are request the status of. Supported attributes include:
+ #
+ # MESSAGES:: the number of messages in the mailbox.
+ # RECENT:: the number of recent messages in the mailbox.
+ # UNSEEN:: the number of unseen messages in the mailbox.
+ #
+ # The return value is a hash of attributes. For example:
+ #
+ # p imap.status("inbox", ["MESSAGES", "RECENT"])
+ # #=> {"RECENT"=>0, "MESSAGES"=>44}
+ #
+ # A Net::IMAP::NoResponseError is raised if status values
+ # for +mailbox+ cannot be returned, for instance because it
+ # does not exist.
+ def status(mailbox, attr)
+ synchronize do
+ send_command("STATUS", mailbox, attr)
+ return @responses.delete("STATUS")[-1].attr
+ end
+ end
+
+ # Sends a APPEND command to append the +message+ to the end of
+ # the +mailbox+. The optional +flags+ argument is an array of
+ # flags to initially passing to the new message. The optional
+ # +date_time+ argument specifies the creation time to assign to the
+ # new message; it defaults to the current time.
+ # For example:
+ #
+ # imap.append("inbox", <<EOF.gsub(/\n/, "\r\n"), [:Seen], Time.now)
+ # Subject: hello
+ # From: shugo@ruby-lang.org
+ # To: shugo@ruby-lang.org
+ #
+ # hello world
+ # EOF
+ #
+ # A Net::IMAP::NoResponseError is raised if the mailbox does
+ # not exist (it is not created automatically), or if the flags,
+ # date_time, or message arguments contain errors.
+ def append(mailbox, message, flags = nil, date_time = nil)
+ args = []
+ if flags
+ args.push(flags)
+ end
+ args.push(date_time) if date_time
+ args.push(Literal.new(message))
+ send_command("APPEND", mailbox, *args)
+ end
+
+ # Sends a CHECK command to request a checkpoint of the currently
+ # selected mailbox. This performs implementation-specific
+ # housekeeping, for instance, reconciling the mailbox's
+ # in-memory and on-disk state.
+ def check
+ send_command("CHECK")
+ end
+
+ # Sends a CLOSE command to close the currently selected mailbox.
+ # The CLOSE command permanently removes from the mailbox all
+ # messages that have the \Deleted flag set.
+ def close
+ send_command("CLOSE")
+ end
+
+ # Sends a EXPUNGE command to permanently remove from the currently
+ # selected mailbox all messages that have the \Deleted flag set.
+ def expunge
+ synchronize do
+ send_command("EXPUNGE")
+ return @responses.delete("EXPUNGE")
+ end
+ end
+
+ # Sends a SEARCH command to search the mailbox for messages that
+ # match the given searching criteria, and returns message sequence
+ # numbers. +keys+ can either be a string holding the entire
+ # search string, or a single-dimension array of search keywords and
+ # arguments. The following are some common search criteria;
+ # see [IMAP] section 6.4.4 for a full list.
+ #
+ # <message set>:: a set of message sequence numbers. ',' indicates
+ # an interval, ':' indicates a range. For instance,
+ # '2,10:12,15' means "2,10,11,12,15".
+ #
+ # BEFORE <date>:: messages with an internal date strictly before
+ # <date>. The date argument has a format similar
+ # to 8-Aug-2002.
+ #
+ # BODY <string>:: messages that contain <string> within their body.
+ #
+ # CC <string>:: messages containing <string> in their CC field.
+ #
+ # FROM <string>:: messages that contain <string> in their FROM field.
+ #
+ # NEW:: messages with the \Recent, but not the \Seen, flag set.
+ #
+ # NOT <search-key>:: negate the following search key.
+ #
+ # OR <search-key> <search-key>:: "or" two search keys together.
+ #
+ # ON <date>:: messages with an internal date exactly equal to <date>,
+ # which has a format similar to 8-Aug-2002.
+ #
+ # SINCE <date>:: messages with an internal date on or after <date>.
+ #
+ # SUBJECT <string>:: messages with <string> in their subject.
+ #
+ # TO <string>:: messages with <string> in their TO field.
+ #
+ # For example:
+ #
+ # p imap.search(["SUBJECT", "hello", "NOT", "NEW"])
+ # #=> [1, 6, 7, 8]
+ def search(keys, charset = nil)
+ return search_internal("SEARCH", keys, charset)
+ end
+
+ # As for #search(), but returns unique identifiers.
+ def uid_search(keys, charset = nil)
+ return search_internal("UID SEARCH", keys, charset)
+ end
+
+ # Sends a FETCH command to retrieve data associated with a message
+ # in the mailbox. The +set+ parameter is a number or an array of
+ # numbers or a Range object. The number is a message sequence
+ # number. +attr+ is a list of attributes to fetch; see the
+ # documentation for Net::IMAP::FetchData for a list of valid
+ # attributes.
+ # The return value is an array of Net::IMAP::FetchData. For example:
+ #
+ # p imap.fetch(6..8, "UID")
+ # #=> [#<Net::IMAP::FetchData seqno=6, attr={"UID"=>98}>, \\
+ # #<Net::IMAP::FetchData seqno=7, attr={"UID"=>99}>, \\
+ # #<Net::IMAP::FetchData seqno=8, attr={"UID"=>100}>]
+ # p imap.fetch(6, "BODY[HEADER.FIELDS (SUBJECT)]")
+ # #=> [#<Net::IMAP::FetchData seqno=6, attr={"BODY[HEADER.FIELDS (SUBJECT)]"=>"Subject: test\r\n\r\n"}>]
+ # data = imap.uid_fetch(98, ["RFC822.SIZE", "INTERNALDATE"])[0]
+ # p data.seqno
+ # #=> 6
+ # p data.attr["RFC822.SIZE"]
+ # #=> 611
+ # p data.attr["INTERNALDATE"]
+ # #=> "12-Oct-2000 22:40:59 +0900"
+ # p data.attr["UID"]
+ # #=> 98
+ def fetch(set, attr)
+ return fetch_internal("FETCH", set, attr)
+ end
+
+ # As for #fetch(), but +set+ contains unique identifiers.
+ def uid_fetch(set, attr)
+ return fetch_internal("UID FETCH", set, attr)
+ end
+
+ # Sends a STORE command to alter data associated with messages
+ # in the mailbox, in particular their flags. The +set+ parameter
+ # is a number or an array of numbers or a Range object. Each number
+ # is a message sequence number. +attr+ is the name of a data item
+ # to store: 'FLAGS' means to replace the message's flag list
+ # with the provided one; '+FLAGS' means to add the provided flags;
+ # and '-FLAGS' means to remove them. +flags+ is a list of flags.
+ #
+ # The return value is an array of Net::IMAP::FetchData. For example:
+ #
+ # p imap.store(6..8, "+FLAGS", [:Deleted])
+ # #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
+ # #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
+ # #<Net::IMAP::FetchData seqno=8, attr={"FLAGS"=>[:Seen, :Deleted]}>]
+ def store(set, attr, flags)
+ return store_internal("STORE", set, attr, flags)
+ end
+
+ # As for #store(), but +set+ contains unique identifiers.
+ def uid_store(set, attr, flags)
+ return store_internal("UID STORE", set, attr, flags)
+ end
+
+ # Sends a COPY command to copy the specified message(s) to the end
+ # of the specified destination +mailbox+. The +set+ parameter is
+ # a number or an array of numbers or a Range object. The number is
+ # a message sequence number.
+ def copy(set, mailbox)
+ copy_internal("COPY", set, mailbox)
+ end
+
+ # As for #copy(), but +set+ contains unique identifiers.
+ def uid_copy(set, mailbox)
+ copy_internal("UID COPY", set, mailbox)
+ end
+
+ # Sends a SORT command to sort messages in the mailbox.
+ # Returns an array of message sequence numbers. For example:
+ #
+ # p imap.sort(["FROM"], ["ALL"], "US-ASCII")
+ # #=> [1, 2, 3, 5, 6, 7, 8, 4, 9]
+ # p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII")
+ # #=> [6, 7, 8, 1]
+ #
+ # See [SORT-THREAD-EXT] for more details.
+ def sort(sort_keys, search_keys, charset)
+ return sort_internal("SORT", sort_keys, search_keys, charset)
+ end
+
+ # As for #sort(), but returns an array of unique identifiers.
+ def uid_sort(sort_keys, search_keys, charset)
+ return sort_internal("UID SORT", sort_keys, search_keys, charset)
+ end
+
+ # Adds a response handler. For example, to detect when
+ # the server sends us a new EXISTS response (which normally
+ # indicates new messages being added to the mail box),
+ # you could add the following handler after selecting the
+ # mailbox.
+ #
+ # imap.add_response_handler { |resp|
+ # if resp.kind_of?(Net::IMAP::UntaggedResponse) and resp.name == "EXISTS"
+ # puts "Mailbox now has #{resp.data} messages"
+ # end
+ # }
+ #
+ def add_response_handler(handler = Proc.new)
+ @response_handlers.push(handler)
+ end
+
+ # Removes the response handler.
+ def remove_response_handler(handler)
+ @response_handlers.delete(handler)
+ end
+
+ # As for #search(), but returns message sequence numbers in threaded
+ # format, as a Net::IMAP::ThreadMember tree. The supported algorithms
+ # are:
+ #
+ # ORDEREDSUBJECT:: split into single-level threads according to subject,
+ # ordered by date.
+ # REFERENCES:: split into threads by parent/child relationships determined
+ # by which message is a reply to which.
+ #
+ # Unlike #search(), +charset+ is a required argument. US-ASCII
+ # and UTF-8 are sample values.
+ #
+ # See [SORT-THREAD-EXT] for more details.
+ def thread(algorithm, search_keys, charset)
+ return thread_internal("THREAD", algorithm, search_keys, charset)
+ end
+
+ # As for #thread(), but returns unique identifiers instead of
+ # message sequence numbers.
+ def uid_thread(algorithm, search_keys, charset)
+ return thread_internal("UID THREAD", algorithm, search_keys, charset)
+ end
+
+ # Decode a string from modified UTF-7 format to UTF-8.
+ #
+ # UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a
+ # slightly modified version of this to encode mailbox names
+ # containing non-ASCII characters; see [IMAP] section 5.1.3.
+ #
+ # Net::IMAP does _not_ automatically encode and decode
+ # mailbox names to and from utf7.
+ def self.decode_utf7(s)
+ return s.gsub(/&(.*?)-/n) {
+ if $1.empty?
+ "&"
+ else
+ base64 = $1.tr(",", "/")
+ x = base64.length % 4
+ if x > 0
+ base64.concat("=" * (4 - x))
+ end
+ u16tou8(base64.unpack("m")[0])
+ end
+ }
+ end
+
+ # Encode a string from UTF-8 format to modified UTF-7.
+ def self.encode_utf7(s)
+ return s.gsub(/(&)|([^\x20-\x25\x27-\x7e]+)/n) { |x|
+ if $1
+ "&-"
+ else
+ base64 = [u8tou16(x)].pack("m")
+ "&" + base64.delete("=\n").tr("/", ",") + "-"
+ end
+ }
+ end
+
+ private
+
+ CRLF = "\r\n" # :nodoc:
+ PORT = 143 # :nodoc:
+
+ @@debug = false
+ @@authenticators = {}
+
+ # Creates a new Net::IMAP object and connects it to the specified
+ # +port+ (143 by default) on the named +host+. If +usessl+ is true,
+ # then an attempt will
+ # be made to use SSL (now TLS) to connect to the server. For this
+ # to work OpenSSL [OSSL] and the Ruby OpenSSL [RSSL]
+ # extensions need to be installed. The +certs+ parameter indicates
+ # the path or file containing the CA cert of the server, and the
+ # +verify+ parameter is for the OpenSSL verification callback.
+ #
+ # The most common errors are:
+ #
+ # Errno::ECONNREFUSED:: connection refused by +host+ or an intervening
+ # firewall.
+ # Errno::ETIMEDOUT:: connection timed out (possibly due to packets
+ # being dropped by an intervening firewall).
+ # Errno::ENETUNREACH:: there is no route to that network.
+ # SocketError:: hostname not known or other socket error.
+ # Net::IMAP::ByeResponseError:: we connected to the host, but they
+ # immediately said goodbye to us.
+ def initialize(host, port = PORT, usessl = false, certs = nil, verify = false)
+ super()
+ @host = host
+ @port = port
+ @tag_prefix = "RUBY"
+ @tagno = 0
+ @parser = ResponseParser.new
+ @sock = TCPSocket.open(host, port)
+ if usessl
+ unless defined?(OpenSSL)
+ raise "SSL extension not installed"
+ end
+ @usessl = true
+
+ # verify the server.
+ context = SSLContext::new()
+ context.ca_file = certs if certs && FileTest::file?(certs)
+ context.ca_path = certs if certs && FileTest::directory?(certs)
+ context.verify_mode = VERIFY_PEER if verify
+ if defined?(VerifyCallbackProc)
+ context.verify_callback = VerifyCallbackProc
+ end
+ @sock = SSLSocket.new(@sock, context)
+ @sock.connect # start ssl session.
+ else
+ @usessl = false
+ end
+ @responses = Hash.new([].freeze)
+ @tagged_responses = {}
+ @response_handlers = []
+ @response_arrival = new_cond
+ @continuation_request = nil
+ @logout_command_tag = nil
+ @debug_output_bol = true
+
+ @greeting = get_response
+ if @greeting.name == "BYE"
+ @sock.close
+ raise ByeResponseError, @greeting.raw_data
+ end
+
+ @client_thread = Thread.current
+ @receiver_thread = Thread.start {
+ receive_responses
+ }
+ end
+
+ def receive_responses
+ while true
+ begin
+ resp = get_response
+ rescue Exception
+ @sock.close
+ @client_thread.raise($!)
+ break
+ end
+ break unless resp
+ begin
+ synchronize do
+ case resp
+ when TaggedResponse
+ @tagged_responses[resp.tag] = resp
+ @response_arrival.broadcast
+ if resp.tag == @logout_command_tag
+ return
+ end
+ when UntaggedResponse
+ record_response(resp.name, resp.data)
+ if resp.data.instance_of?(ResponseText) &&
+ (code = resp.data.code)
+ record_response(code.name, code.data)
+ end
+ if resp.name == "BYE" && @logout_command_tag.nil?
+ @sock.close
+ raise ByeResponseError, resp.raw_data
+ end
+ when ContinuationRequest
+ @continuation_request = resp
+ @response_arrival.broadcast
+ end
+ @response_handlers.each do |handler|
+ handler.call(resp)
+ end
+ end
+ rescue Exception
+ @client_thread.raise($!)
+ end
+ end
+ end
+
+ def get_tagged_response(tag)
+ until @tagged_responses.key?(tag)
+ @response_arrival.wait
+ end
+ return pick_up_tagged_response(tag)
+ end
+
+ def pick_up_tagged_response(tag)
+ resp = @tagged_responses.delete(tag)
+ case resp.name
+ when /\A(?:NO)\z/ni
+ raise NoResponseError, resp.data.text
+ when /\A(?:BAD)\z/ni
+ raise BadResponseError, resp.data.text
+ else
+ return resp
+ end
+ end
+
+ def get_response
+ buff = ""
+ while true
+ s = @sock.gets(CRLF)
+ break unless s
+ buff.concat(s)
+ if /\{(\d+)\}\r\n/n =~ s
+ s = @sock.read($1.to_i)
+ buff.concat(s)
+ else
+ break
+ end
+ end
+ return nil if buff.length == 0
+ if @@debug
+ $stderr.print(buff.gsub(/^/n, "S: "))
+ end
+ return @parser.parse(buff)
+ end
+
+ def record_response(name, data)
+ unless @responses.has_key?(name)
+ @responses[name] = []
+ end
+ @responses[name].push(data)
+ end
+
+ def send_command(cmd, *args, &block)
+ synchronize do
+ tag = Thread.current[:net_imap_tag] = generate_tag
+ put_string(tag + " " + cmd)
+ args.each do |i|
+ put_string(" ")
+ send_data(i)
+ end
+ put_string(CRLF)
+ if cmd == "LOGOUT"
+ @logout_command_tag = tag
+ end
+ if block
+ add_response_handler(block)
+ end
+ begin
+ return get_tagged_response(tag)
+ ensure
+ if block
+ remove_response_handler(block)
+ end
+ end
+ end
+ end
+
+ def generate_tag
+ @tagno += 1
+ return format("%s%04d", @tag_prefix, @tagno)
+ end
+
+ def put_string(str)
+ @sock.print(str)
+ if @@debug
+ if @debug_output_bol
+ $stderr.print("C: ")
+ end
+ $stderr.print(str.gsub(/\n(?!\z)/n, "\nC: "))
+ if /\r\n\z/n.match(str)
+ @debug_output_bol = true
+ else
+ @debug_output_bol = false
+ end
+ end
+ end
+
+ def send_data(data)
+ case data
+ when nil
+ put_string("NIL")
+ when String
+ send_string_data(data)
+ when Integer
+ send_number_data(data)
+ when Array
+ send_list_data(data)
+ when Time
+ send_time_data(data)
+ when Symbol
+ send_symbol_data(data)
+ else
+ data.send_data(self)
+ end
+ end
+
+ def send_string_data(str)
+ case str
+ when ""
+ put_string('""')
+ when /[\x80-\xff\r\n]/n
+ # literal
+ send_literal(str)
+ when /[(){ \x00-\x1f\x7f%*"\\]/n
+ # quoted string
+ send_quoted_string(str)
+ else
+ put_string(str)
+ end
+ end
+
+ def send_quoted_string(str)
+ put_string('"' + str.gsub(/["\\]/n, "\\\\\\&") + '"')
+ end
+
+ def send_literal(str)
+ put_string("{" + str.length.to_s + "}" + CRLF)
+ while @continuation_request.nil? &&
+ !@tagged_responses.key?(Thread.current[:net_imap_tag])
+ @response_arrival.wait
+ end
+ if @continuation_request.nil?
+ pick_up_tagged_response(Thread.current[:net_imap_tag])
+ raise ResponseError.new("expected continuation request")
+ end
+ @continuation_request = nil
+ put_string(str)
+ end
+
+ def send_number_data(num)
+ if num < 0 || num >= 4294967296
+ raise DataFormatError, num.to_s
+ end
+ put_string(num.to_s)
+ end
+
+ def send_list_data(list)
+ put_string("(")
+ first = true
+ list.each do |i|
+ if first
+ first = false
+ else
+ put_string(" ")
+ end
+ send_data(i)
+ end
+ put_string(")")
+ end
+
+ DATE_MONTH = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
+
+ def send_time_data(time)
+ t = time.dup.gmtime
+ s = format('"%2d-%3s-%4d %02d:%02d:%02d +0000"',
+ t.day, DATE_MONTH[t.month - 1], t.year,
+ t.hour, t.min, t.sec)
+ put_string(s)
+ end
+
+ def send_symbol_data(symbol)
+ put_string("\\" + symbol.to_s)
+ end
+
+ def search_internal(cmd, keys, charset)
+ if keys.instance_of?(String)
+ keys = [RawData.new(keys)]
+ else
+ normalize_searching_criteria(keys)
+ end
+ synchronize do
+ if charset
+ send_command(cmd, "CHARSET", charset, *keys)
+ else
+ send_command(cmd, *keys)
+ end
+ return @responses.delete("SEARCH")[-1]
+ end
+ end
+
+ def fetch_internal(cmd, set, attr)
+ if attr.instance_of?(String)
+ attr = RawData.new(attr)
+ end
+ synchronize do
+ @responses.delete("FETCH")
+ send_command(cmd, MessageSet.new(set), attr)
+ return @responses.delete("FETCH")
+ end
+ end
+
+ def store_internal(cmd, set, attr, flags)
+ if attr.instance_of?(String)
+ attr = RawData.new(attr)
+ end
+ synchronize do
+ @responses.delete("FETCH")
+ send_command(cmd, MessageSet.new(set), attr, flags)
+ return @responses.delete("FETCH")
+ end
+ end
+
+ def copy_internal(cmd, set, mailbox)
+ send_command(cmd, MessageSet.new(set), mailbox)
+ end
+
+ def sort_internal(cmd, sort_keys, search_keys, charset)
+ if search_keys.instance_of?(String)
+ search_keys = [RawData.new(search_keys)]
+ else
+ normalize_searching_criteria(search_keys)
+ end
+ normalize_searching_criteria(search_keys)
+ synchronize do
+ send_command(cmd, sort_keys, charset, *search_keys)
+ return @responses.delete("SORT")[-1]
+ end
+ end
+
+ def thread_internal(cmd, algorithm, search_keys, charset)
+ if search_keys.instance_of?(String)
+ search_keys = [RawData.new(search_keys)]
+ else
+ normalize_searching_criteria(search_keys)
+ end
+ normalize_searching_criteria(search_keys)
+ send_command(cmd, algorithm, charset, *search_keys)
+ return @responses.delete("THREAD")[-1]
+ end
+
+ def normalize_searching_criteria(keys)
+ keys.collect! do |i|
+ case i
+ when -1, Range, Array
+ MessageSet.new(i)
+ else
+ i
+ end
+ end
+ end
+
+ def self.u16tou8(s)
+ len = s.length
+ if len < 2
+ return ""
+ end
+ buf = ""
+ i = 0
+ while i < len
+ c = s[i] << 8 | s[i + 1]
+ i += 2
+ if c == 0xfeff
+ next
+ elsif c < 0x0080
+ buf.concat(c)
+ elsif c < 0x0800
+ b2 = c & 0x003f
+ b1 = c >> 6
+ buf.concat(b1 | 0xc0)
+ buf.concat(b2 | 0x80)
+ elsif c >= 0xdc00 && c < 0xe000
+ raise DataFormatError, "invalid surrogate detected"
+ elsif c >= 0xd800 && c < 0xdc00
+ if i + 2 > len
+ raise DataFormatError, "invalid surrogate detected"
+ end
+ low = s[i] << 8 | s[i + 1]
+ i += 2
+ if low < 0xdc00 || low > 0xdfff
+ raise DataFormatError, "invalid surrogate detected"
+ end
+ c = (((c & 0x03ff)) << 10 | (low & 0x03ff)) + 0x10000
+ b4 = c & 0x003f
+ b3 = (c >> 6) & 0x003f
+ b2 = (c >> 12) & 0x003f
+ b1 = c >> 18;
+ buf.concat(b1 | 0xf0)
+ buf.concat(b2 | 0x80)
+ buf.concat(b3 | 0x80)
+ buf.concat(b4 | 0x80)
+ else # 0x0800-0xffff
+ b3 = c & 0x003f
+ b2 = (c >> 6) & 0x003f
+ b1 = c >> 12
+ buf.concat(b1 | 0xe0)
+ buf.concat(b2 | 0x80)
+ buf.concat(b3 | 0x80)
+ end
+ end
+ return buf
+ end
+ private_class_method :u16tou8
+
+ def self.u8tou16(s)
+ len = s.length
+ buf = ""
+ i = 0
+ while i < len
+ c = s[i]
+ if (c & 0x80) == 0
+ buf.concat(0x00)
+ buf.concat(c)
+ i += 1
+ elsif (c & 0xe0) == 0xc0 &&
+ len >= 2 &&
+ (s[i + 1] & 0xc0) == 0x80
+ if c == 0xc0 || c == 0xc1
+ raise DataFormatError, format("non-shortest UTF-8 sequence (%02x)", c)
+ end
+ u = ((c & 0x1f) << 6) | (s[i + 1] & 0x3f)
+ buf.concat(u >> 8)
+ buf.concat(u & 0x00ff)
+ i += 2
+ elsif (c & 0xf0) == 0xe0 &&
+ i + 2 < len &&
+ (s[i + 1] & 0xc0) == 0x80 &&
+ (s[i + 2] & 0xc0) == 0x80
+ if c == 0xe0 && s[i + 1] < 0xa0
+ raise DataFormatError, format("non-shortest UTF-8 sequence (%02x)", c)
+ end
+ u = ((c & 0x0f) << 12) | ((s[i + 1] & 0x3f) << 6) | (s[i + 2] & 0x3f)
+ # surrogate chars
+ if u >= 0xd800 && u <= 0xdfff
+ raise DataFormatError, format("none-UTF-16 char detected (%04x)", u)
+ end
+ buf.concat(u >> 8)
+ buf.concat(u & 0x00ff)
+ i += 3
+ elsif (c & 0xf8) == 0xf0 &&
+ i + 3 < len &&
+ (s[i + 1] & 0xc0) == 0x80 &&
+ (s[i + 2] & 0xc0) == 0x80 &&
+ (s[i + 3] & 0xc0) == 0x80
+ if c == 0xf0 && s[i + 1] < 0x90
+ raise DataFormatError, format("non-shortest UTF-8 sequence (%02x)", c)
+ end
+ u = ((c & 0x07) << 18) | ((s[i + 1] & 0x3f) << 12) |
+ ((s[i + 2] & 0x3f) << 6) | (s[i + 3] & 0x3f)
+ if u < 0x10000
+ buf.concat(u >> 8)
+ buf.concat(u & 0x00ff)
+ elsif u < 0x110000
+ high = ((u - 0x10000) >> 10) | 0xd800
+ low = (u & 0x03ff) | 0xdc00
+ buf.concat(high >> 8)
+ buf.concat(high & 0x00ff)
+ buf.concat(low >> 8)
+ buf.concat(low & 0x00ff)
+ else
+ raise DataFormatError, format("none-UTF-16 char detected (%04x)", u)
+ end
+ i += 4
+ else
+ raise DataFormatError, format("illegal UTF-8 sequence (%02x)", c)
+ end
+ end
+ return buf
+ end
+ private_class_method :u8tou16
+
+ class RawData # :nodoc:
+ def send_data(imap)
+ imap.send(:put_string, @data)
+ end
+
+ private
+
+ def initialize(data)
+ @data = data
+ end
+ end
+
+ class Atom # :nodoc:
+ def send_data(imap)
+ imap.send(:put_string, @data)
+ end
+
+ private
+
+ def initialize(data)
+ @data = data
+ end
+ end
+
+ class QuotedString # :nodoc:
+ def send_data(imap)
+ imap.send(:send_quoted_string, @data)
+ end
+
+ private
+
+ def initialize(data)
+ @data = data
+ end
+ end
+
+ class Literal # :nodoc:
+ def send_data(imap)
+ imap.send(:send_literal, @data)
+ end
+
+ private
+
+ def initialize(data)
+ @data = data
+ end
+ end
+
+ class MessageSet # :nodoc:
+ def send_data(imap)
+ imap.send(:put_string, format_internal(@data))
+ end
+
+ private
+
+ def initialize(data)
+ @data = data
+ end
+
+ def format_internal(data)
+ case data
+ when "*"
+ return data
+ when Integer
+ ensure_nz_number(data)
+ if data == -1
+ return "*"
+ else
+ return data.to_s
+ end
+ when Range
+ return format_internal(data.first) +
+ ":" + format_internal(data.last)
+ when Array
+ return data.collect {|i| format_internal(i)}.join(",")
+ when ThreadMember
+ return data.seqno.to_s +
+ ":" + data.children.collect {|i| format_internal(i).join(",")}
+ else
+ raise DataFormatError, data.inspect
+ end
+ end
+
+ def ensure_nz_number(num)
+ if num < -1 || num == 0 || num >= 4294967296
+ msg = "nz_number must be non-zero unsigned 32-bit integer: " +
+ num.inspect
+ raise DataFormatError, msg
+ end
+ end
+ end
+
+ # Net::IMAP::ContinuationRequest represents command continuation requests.
+ #
+ # The command continuation request response is indicated by a "+" token
+ # instead of a tag. This form of response indicates that the server is
+ # ready to accept the continuation of a command from the client. The
+ # remainder of this response is a line of text.
+ #
+ # continue_req ::= "+" SPACE (resp_text / base64)
+ #
+ # ==== Fields:
+ #
+ # data:: Returns the data (Net::IMAP::ResponseText).
+ #
+ # raw_data:: Returns the raw data string.
+ ContinuationRequest = Struct.new(:data, :raw_data)
+
+ # Net::IMAP::UntaggedResponse represents untagged responses.
+ #
+ # Data transmitted by the server to the client and status responses
+ # that do not indicate command completion are prefixed with the token
+ # "*", and are called untagged responses.
+ #
+ # response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye /
+ # mailbox_data / message_data / capability_data)
+ #
+ # ==== Fields:
+ #
+ # name:: Returns the name such as "FLAGS", "LIST", "FETCH"....
+ #
+ # data:: Returns the data such as an array of flag symbols,
+ # a ((<Net::IMAP::MailboxList>)) object....
+ #
+ # raw_data:: Returns the raw data string.
+ UntaggedResponse = Struct.new(:name, :data, :raw_data)
+
+ # Net::IMAP::TaggedResponse represents tagged responses.
+ #
+ # The server completion result response indicates the success or
+ # failure of the operation. It is tagged with the same tag as the
+ # client command which began the operation.
+ #
+ # response_tagged ::= tag SPACE resp_cond_state CRLF
+ #
+ # tag ::= 1*<any ATOM_CHAR except "+">
+ #
+ # resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
+ #
+ # ==== Fields:
+ #
+ # tag:: Returns the tag.
+ #
+ # name:: Returns the name. the name is one of "OK", "NO", "BAD".
+ #
+ # data:: Returns the data. See ((<Net::IMAP::ResponseText>)).
+ #
+ # raw_data:: Returns the raw data string.
+ #
+ TaggedResponse = Struct.new(:tag, :name, :data, :raw_data)
+
+ # Net::IMAP::ResponseText represents texts of responses.
+ # The text may be prefixed by the response code.
+ #
+ # resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text)
+ # ;; text SHOULD NOT begin with "[" or "="
+ #
+ # ==== Fields:
+ #
+ # code:: Returns the response code. See ((<Net::IMAP::ResponseCode>)).
+ #
+ # text:: Returns the text.
+ #
+ ResponseText = Struct.new(:code, :text)
+
+ #
+ # Net::IMAP::ResponseCode represents response codes.
+ #
+ # resp_text_code ::= "ALERT" / "PARSE" /
+ # "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" /
+ # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
+ # "UIDVALIDITY" SPACE nz_number /
+ # "UNSEEN" SPACE nz_number /
+ # atom [SPACE 1*<any TEXT_CHAR except "]">]
+ #
+ # ==== Fields:
+ #
+ # name:: Returns the name such as "ALERT", "PERMANENTFLAGS", "UIDVALIDITY"....
+ #
+ # data:: Returns the data if it exists.
+ #
+ ResponseCode = Struct.new(:name, :data)
+
+ # Net::IMAP::MailboxList represents contents of the LIST response.
+ #
+ # mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
+ # "\Noselect" / "\Unmarked" / flag_extension) ")"
+ # SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
+ #
+ # ==== Fields:
+ #
+ # attr:: Returns the name attributes. Each name attribute is a symbol
+ # capitalized by String#capitalize, such as :Noselect (not :NoSelect).
+ #
+ # delim:: Returns the hierarchy delimiter
+ #
+ # name:: Returns the mailbox name.
+ #
+ MailboxList = Struct.new(:attr, :delim, :name)
+
+ # Net::IMAP::MailboxQuota represents contents of GETQUOTA response.
+ # This object can also be a response to GETQUOTAROOT. In the syntax
+ # specification below, the delimiter used with the "#" construct is a
+ # single space (SPACE).
+ #
+ # quota_list ::= "(" #quota_resource ")"
+ #
+ # quota_resource ::= atom SPACE number SPACE number
+ #
+ # quota_response ::= "QUOTA" SPACE astring SPACE quota_list
+ #
+ # ==== Fields:
+ #
+ # mailbox:: The mailbox with the associated quota.
+ #
+ # usage:: Current storage usage of mailbox.
+ #
+ # quota:: Quota limit imposed on mailbox.
+ #
+ MailboxQuota = Struct.new(:mailbox, :usage, :quota)
+
+ # Net::IMAP::MailboxQuotaRoot represents part of the GETQUOTAROOT
+ # response. (GETQUOTAROOT can also return Net::IMAP::MailboxQuota.)
+ #
+ # quotaroot_response ::= "QUOTAROOT" SPACE astring *(SPACE astring)
+ #
+ # ==== Fields:
+ #
+ # mailbox:: The mailbox with the associated quota.
+ #
+ # quotaroots:: Zero or more quotaroots that effect the quota on the
+ # specified mailbox.
+ #
+ MailboxQuotaRoot = Struct.new(:mailbox, :quotaroots)
+
+ # Net::IMAP::MailboxACLItem represents response from GETACL.
+ #
+ # acl_data ::= "ACL" SPACE mailbox *(SPACE identifier SPACE rights)
+ #
+ # identifier ::= astring
+ #
+ # rights ::= astring
+ #
+ # ==== Fields:
+ #
+ # user:: Login name that has certain rights to the mailbox
+ # that was specified with the getacl command.
+ #
+ # rights:: The access rights the indicated user has to the
+ # mailbox.
+ #
+ MailboxACLItem = Struct.new(:user, :rights)
+
+ # Net::IMAP::StatusData represents contents of the STATUS response.
+ #
+ # ==== Fields:
+ #
+ # mailbox:: Returns the mailbox name.
+ #
+ # attr:: Returns a hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT",
+ # "UIDVALIDITY", "UNSEEN". Each value is a number.
+ #
+ StatusData = Struct.new(:mailbox, :attr)
+
+ # Net::IMAP::FetchData represents contents of the FETCH response.
+ #
+ # ==== Fields:
+ #
+ # seqno:: Returns the message sequence number.
+ # (Note: not the unique identifier, even for the UID command response.)
+ #
+ # attr:: Returns a hash. Each key is a data item name, and each value is
+ # its value.
+ #
+ # The current data items are:
+ #
+ # [BODY]
+ # A form of BODYSTRUCTURE without extension data.
+ # [BODY[<section>]<<origin_octet>>]
+ # A string expressing the body contents of the specified section.
+ # [BODYSTRUCTURE]
+ # An object that describes the [MIME-IMB] body structure of a message.
+ # See Net::IMAP::BodyTypeBasic, Net::IMAP::BodyTypeText,
+ # Net::IMAP::BodyTypeMessage, Net::IMAP::BodyTypeMultipart.
+ # [ENVELOPE]
+ # A Net::IMAP::Envelope object that describes the envelope
+ # structure of a message.
+ # [FLAGS]
+ # A array of flag symbols that are set for this message. flag symbols
+ # are capitalized by String#capitalize.
+ # [INTERNALDATE]
+ # A string representing the internal date of the message.
+ # [RFC822]
+ # Equivalent to BODY[].
+ # [RFC822.HEADER]
+ # Equivalent to BODY.PEEK[HEADER].
+ # [RFC822.SIZE]
+ # A number expressing the [RFC-822] size of the message.
+ # [RFC822.TEXT]
+ # Equivalent to BODY[TEXT].
+ # [UID]
+ # A number expressing the unique identifier of the message.
+ #
+ FetchData = Struct.new(:seqno, :attr)
+
+ # Net::IMAP::Envelope represents envelope structures of messages.
+ #
+ # ==== Fields:
+ #
+ # date:: Returns a string that represents the date.
+ #
+ # subject:: Returns a string that represents the subject.
+ #
+ # from:: Returns an array of Net::IMAP::Address that represents the from.
+ #
+ # sender:: Returns an array of Net::IMAP::Address that represents the sender.
+ #
+ # reply_to:: Returns an array of Net::IMAP::Address that represents the reply-to.
+ #
+ # to:: Returns an array of Net::IMAP::Address that represents the to.
+ #
+ # cc:: Returns an array of Net::IMAP::Address that represents the cc.
+ #
+ # bcc:: Returns an array of Net::IMAP::Address that represents the bcc.
+ #
+ # in_reply_to:: Returns a string that represents the in-reply-to.
+ #
+ # message_id:: Returns a string that represents the message-id.
+ #
+ Envelope = Struct.new(:date, :subject, :from, :sender, :reply_to,
+ :to, :cc, :bcc, :in_reply_to, :message_id)
+
+ #
+ # Net::IMAP::Address represents electronic mail addresses.
+ #
+ # ==== Fields:
+ #
+ # name:: Returns the phrase from [RFC-822] mailbox.
+ #
+ # route:: Returns the route from [RFC-822] route-addr.
+ #
+ # mailbox:: nil indicates end of [RFC-822] group.
+ # If non-nil and host is nil, returns [RFC-822] group name.
+ # Otherwise, returns [RFC-822] local-part
+ #
+ # host:: nil indicates [RFC-822] group syntax.
+ # Otherwise, returns [RFC-822] domain name.
+ #
+ Address = Struct.new(:name, :route, :mailbox, :host)
+
+ #
+ # Net::IMAP::ContentDisposition represents Content-Disposition fields.
+ #
+ # ==== Fields:
+ #
+ # dsp_type:: Returns the disposition type.
+ #
+ # param:: Returns a hash that represents parameters of the Content-Disposition
+ # field.
+ #
+ ContentDisposition = Struct.new(:dsp_type, :param)
+
+ # Net::IMAP::ThreadMember represents a thread-node returned
+ # by Net::IMAP#thread
+ #
+ # ==== Fields:
+ #
+ # seqno:: The sequence number of this message.
+ #
+ # children:: an array of Net::IMAP::ThreadMember objects for mail
+ # items that are children of this in the thread.
+ #
+ ThreadMember = Struct.new(:seqno, :children)
+
+ # Net::IMAP::BodyTypeBasic represents basic body structures of messages.
+ #
+ # ==== Fields:
+ #
+ # media_type:: Returns the content media type name as defined in [MIME-IMB].
+ #
+ # subtype:: Returns the content subtype name as defined in [MIME-IMB].
+ #
+ # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
+ #
+ # content_id:: Returns a string giving the content id as defined in [MIME-IMB].
+ #
+ # description:: Returns a string giving the content description as defined in
+ # [MIME-IMB].
+ #
+ # encoding:: Returns a string giving the content transfer encoding as defined in
+ # [MIME-IMB].
+ #
+ # size:: Returns a number giving the size of the body in octets.
+ #
+ # md5:: Returns a string giving the body MD5 value as defined in [MD5].
+ #
+ # disposition:: Returns a Net::IMAP::ContentDisposition object giving
+ # the content disposition.
+ #
+ # language:: Returns a string or an array of strings giving the body
+ # language value as defined in [LANGUAGE-TAGS].
+ #
+ # extension:: Returns extension data.
+ #
+ # multipart?:: Returns false.
+ #
+ class BodyTypeBasic < Struct.new(:media_type, :subtype,
+ :param, :content_id,
+ :description, :encoding, :size,
+ :md5, :disposition, :language,
+ :extension)
+ def multipart?
+ return false
+ end
+
+ # Obsolete: use +subtype+ instead. Calling this will
+ # generate a warning message to +stderr+, then return
+ # the value of +subtype+.
+ def media_subtype
+ $stderr.printf("warning: media_subtype is obsolete.\n")
+ $stderr.printf(" use subtype instead.\n")
+ return subtype
+ end
+ end
+
+ # Net::IMAP::BodyTypeText represents TEXT body structures of messages.
+ #
+ # ==== Fields:
+ #
+ # lines:: Returns the size of the body in text lines.
+ #
+ # And Net::IMAP::BodyTypeText has all fields of Net::IMAP::BodyTypeBasic.
+ #
+ class BodyTypeText < Struct.new(:media_type, :subtype,
+ :param, :content_id,
+ :description, :encoding, :size,
+ :lines,
+ :md5, :disposition, :language,
+ :extension)
+ def multipart?
+ return false
+ end
+
+ # Obsolete: use +subtype+ instead. Calling this will
+ # generate a warning message to +stderr+, then return
+ # the value of +subtype+.
+ def media_subtype
+ $stderr.printf("warning: media_subtype is obsolete.\n")
+ $stderr.printf(" use subtype instead.\n")
+ return subtype
+ end
+ end
+
+ # Net::IMAP::BodyTypeMessage represents MESSAGE/RFC822 body structures of messages.
+ #
+ # ==== Fields:
+ #
+ # envelope:: Returns a Net::IMAP::Envelope giving the envelope structure.
+ #
+ # body:: Returns an object giving the body structure.
+ #
+ # And Net::IMAP::BodyTypeMessage has all methods of Net::IMAP::BodyTypeText.
+ #
+ class BodyTypeMessage < Struct.new(:media_type, :subtype,
+ :param, :content_id,
+ :description, :encoding, :size,
+ :envelope, :body, :lines,
+ :md5, :disposition, :language,
+ :extension)
+ def multipart?
+ return false
+ end
+
+ # Obsolete: use +subtype+ instead. Calling this will
+ # generate a warning message to +stderr+, then return
+ # the value of +subtype+.
+ def media_subtype
+ $stderr.printf("warning: media_subtype is obsolete.\n")
+ $stderr.printf(" use subtype instead.\n")
+ return subtype
+ end
+ end
+
+ # Net::IMAP::BodyTypeMultipart represents multipart body structures
+ # of messages.
+ #
+ # ==== Fields:
+ #
+ # media_type:: Returns the content media type name as defined in [MIME-IMB].
+ #
+ # subtype:: Returns the content subtype name as defined in [MIME-IMB].
+ #
+ # parts:: Returns multiple parts.
+ #
+ # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
+ #
+ # disposition:: Returns a Net::IMAP::ContentDisposition object giving
+ # the content disposition.
+ #
+ # language:: Returns a string or an array of strings giving the body
+ # language value as defined in [LANGUAGE-TAGS].
+ #
+ # extension:: Returns extension data.
+ #
+ # multipart?:: Returns true.
+ #
+ class BodyTypeMultipart < Struct.new(:media_type, :subtype,
+ :parts,
+ :param, :disposition, :language,
+ :extension)
+ def multipart?
+ return true
+ end
+
+ # Obsolete: use +subtype+ instead. Calling this will
+ # generate a warning message to +stderr+, then return
+ # the value of +subtype+.
+ def media_subtype
+ $stderr.printf("warning: media_subtype is obsolete.\n")
+ $stderr.printf(" use subtype instead.\n")
+ return subtype
+ end
+ end
+
+ class ResponseParser # :nodoc:
+ def parse(str)
+ @str = str
+ @pos = 0
+ @lex_state = EXPR_BEG
+ @token = nil
+ return response
+ end
+
+ private
+
+ EXPR_BEG = :EXPR_BEG
+ EXPR_DATA = :EXPR_DATA
+ EXPR_TEXT = :EXPR_TEXT
+ EXPR_RTEXT = :EXPR_RTEXT
+ EXPR_CTEXT = :EXPR_CTEXT
+
+ T_SPACE = :SPACE
+ T_NIL = :NIL
+ T_NUMBER = :NUMBER
+ T_ATOM = :ATOM
+ T_QUOTED = :QUOTED
+ T_LPAR = :LPAR
+ T_RPAR = :RPAR
+ T_BSLASH = :BSLASH
+ T_STAR = :STAR
+ T_LBRA = :LBRA
+ T_RBRA = :RBRA
+ T_LITERAL = :LITERAL
+ T_PLUS = :PLUS
+ T_PERCENT = :PERCENT
+ T_CRLF = :CRLF
+ T_EOF = :EOF
+ T_TEXT = :TEXT
+
+ BEG_REGEXP = /\G(?:\
+(?# 1: SPACE )( +)|\
+(?# 2: NIL )(NIL)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
+(?# 3: NUMBER )(\d+)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
+(?# 4: ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+]+)|\
+(?# 5: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
+(?# 6: LPAR )(\()|\
+(?# 7: RPAR )(\))|\
+(?# 8: BSLASH )(\\)|\
+(?# 9: STAR )(\*)|\
+(?# 10: LBRA )(\[)|\
+(?# 11: RBRA )(\])|\
+(?# 12: LITERAL )\{(\d+)\}\r\n|\
+(?# 13: PLUS )(\+)|\
+(?# 14: PERCENT )(%)|\
+(?# 15: CRLF )(\r\n)|\
+(?# 16: EOF )(\z))/ni
+
+ DATA_REGEXP = /\G(?:\
+(?# 1: SPACE )( )|\
+(?# 2: NIL )(NIL)|\
+(?# 3: NUMBER )(\d+)|\
+(?# 4: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
+(?# 5: LITERAL )\{(\d+)\}\r\n|\
+(?# 6: LPAR )(\()|\
+(?# 7: RPAR )(\)))/ni
+
+ TEXT_REGEXP = /\G(?:\
+(?# 1: TEXT )([^\x00\r\n]*))/ni
+
+ RTEXT_REGEXP = /\G(?:\
+(?# 1: LBRA )(\[)|\
+(?# 2: TEXT )([^\x00\r\n]*))/ni
+
+ CTEXT_REGEXP = /\G(?:\
+(?# 1: TEXT )([^\x00\r\n\]]*))/ni
+
+ Token = Struct.new(:symbol, :value)
+
+ def response
+ token = lookahead
+ case token.symbol
+ when T_PLUS
+ result = continue_req
+ when T_STAR
+ result = response_untagged
+ else
+ result = response_tagged
+ end
+ match(T_CRLF)
+ match(T_EOF)
+ return result
+ end
+
+ def continue_req
+ match(T_PLUS)
+ match(T_SPACE)
+ return ContinuationRequest.new(resp_text, @str)
+ end
+
+ def response_untagged
+ match(T_STAR)
+ match(T_SPACE)
+ token = lookahead
+ if token.symbol == T_NUMBER
+ return numeric_response
+ elsif token.symbol == T_ATOM
+ case token.value
+ when /\A(?:OK|NO|BAD|BYE|PREAUTH)\z/ni
+ return response_cond
+ when /\A(?:FLAGS)\z/ni
+ return flags_response
+ when /\A(?:LIST|LSUB)\z/ni
+ return list_response
+ when /\A(?:QUOTA)\z/ni
+ return getquota_response
+ when /\A(?:QUOTAROOT)\z/ni
+ return getquotaroot_response
+ when /\A(?:ACL)\z/ni
+ return getacl_response
+ when /\A(?:SEARCH|SORT)\z/ni
+ return search_response
+ when /\A(?:THREAD)\z/ni
+ return thread_response
+ when /\A(?:STATUS)\z/ni
+ return status_response
+ when /\A(?:CAPABILITY)\z/ni
+ return capability_response
+ else
+ return text_response
+ end
+ else
+ parse_error("unexpected token %s", token.symbol)
+ end
+ end
+
+ def response_tagged
+ tag = atom
+ match(T_SPACE)
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ return TaggedResponse.new(tag, name, resp_text, @str)
+ end
+
+ def response_cond
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ return UntaggedResponse.new(name, resp_text, @str)
+ end
+
+ def numeric_response
+ n = number
+ match(T_SPACE)
+ token = match(T_ATOM)
+ name = token.value.upcase
+ case name
+ when "EXISTS", "RECENT", "EXPUNGE"
+ return UntaggedResponse.new(name, n, @str)
+ when "FETCH"
+ shift_token
+ match(T_SPACE)
+ data = FetchData.new(n, msg_att)
+ return UntaggedResponse.new(name, data, @str)
+ end
+ end
+
+ def msg_att
+ match(T_LPAR)
+ attr = {}
+ while true
+ token = lookahead
+ case token.symbol
+ when T_RPAR
+ shift_token
+ break
+ when T_SPACE
+ shift_token
+ token = lookahead
+ end
+ case token.value
+ when /\A(?:ENVELOPE)\z/ni
+ name, val = envelope_data
+ when /\A(?:FLAGS)\z/ni
+ name, val = flags_data
+ when /\A(?:INTERNALDATE)\z/ni
+ name, val = internaldate_data
+ when /\A(?:RFC822(?:\.HEADER|\.TEXT)?)\z/ni
+ name, val = rfc822_text
+ when /\A(?:RFC822\.SIZE)\z/ni
+ name, val = rfc822_size
+ when /\A(?:BODY(?:STRUCTURE)?)\z/ni
+ name, val = body_data
+ when /\A(?:UID)\z/ni
+ name, val = uid_data
+ else
+ parse_error("unknown attribute `%s'", token.value)
+ end
+ attr[name] = val
+ end
+ return attr
+ end
+
+ def envelope_data
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ return name, envelope
+ end
+
+ def envelope
+ @lex_state = EXPR_DATA
+ token = lookahead
+ if token.symbol == T_NIL
+ shift_token
+ result = nil
+ else
+ match(T_LPAR)
+ date = nstring
+ match(T_SPACE)
+ subject = nstring
+ match(T_SPACE)
+ from = address_list
+ match(T_SPACE)
+ sender = address_list
+ match(T_SPACE)
+ reply_to = address_list
+ match(T_SPACE)
+ to = address_list
+ match(T_SPACE)
+ cc = address_list
+ match(T_SPACE)
+ bcc = address_list
+ match(T_SPACE)
+ in_reply_to = nstring
+ match(T_SPACE)
+ message_id = nstring
+ match(T_RPAR)
+ result = Envelope.new(date, subject, from, sender, reply_to,
+ to, cc, bcc, in_reply_to, message_id)
+ end
+ @lex_state = EXPR_BEG
+ return result
+ end
+
+ def flags_data
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ return name, flag_list
+ end
+
+ def internaldate_data
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ token = match(T_QUOTED)
+ return name, token.value
+ end
+
+ def rfc822_text
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ return name, nstring
+ end
+
+ def rfc822_size
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ return name, number
+ end
+
+ def body_data
+ token = match(T_ATOM)
+ name = token.value.upcase
+ token = lookahead
+ if token.symbol == T_SPACE
+ shift_token
+ return name, body
+ end
+ name.concat(section)
+ token = lookahead
+ if token.symbol == T_ATOM
+ name.concat(token.value)
+ shift_token
+ end
+ match(T_SPACE)
+ data = nstring
+ return name, data
+ end
+
+ def body
+ @lex_state = EXPR_DATA
+ token = lookahead
+ if token.symbol == T_NIL
+ shift_token
+ result = nil
+ else
+ match(T_LPAR)
+ token = lookahead
+ if token.symbol == T_LPAR
+ result = body_type_mpart
+ else
+ result = body_type_1part
+ end
+ match(T_RPAR)
+ end
+ @lex_state = EXPR_BEG
+ return result
+ end
+
+ def body_type_1part
+ token = lookahead
+ case token.value
+ when /\A(?:TEXT)\z/ni
+ return body_type_text
+ when /\A(?:MESSAGE)\z/ni
+ return body_type_msg
+ else
+ return body_type_basic
+ end
+ end
+
+ def body_type_basic
+ mtype, msubtype = media_type
+ token = lookahead
+ if token.symbol == T_RPAR
+ return BodyTypeBasic.new(mtype, msubtype)
+ end
+ match(T_SPACE)
+ param, content_id, desc, enc, size = body_fields
+ md5, disposition, language, extension = body_ext_1part
+ return BodyTypeBasic.new(mtype, msubtype,
+ param, content_id,
+ desc, enc, size,
+ md5, disposition, language, extension)
+ end
+
+ def body_type_text
+ mtype, msubtype = media_type
+ match(T_SPACE)
+ param, content_id, desc, enc, size = body_fields
+ match(T_SPACE)
+ lines = number
+ md5, disposition, language, extension = body_ext_1part
+ return BodyTypeText.new(mtype, msubtype,
+ param, content_id,
+ desc, enc, size,
+ lines,
+ md5, disposition, language, extension)
+ end
+
+ def body_type_msg
+ mtype, msubtype = media_type
+ match(T_SPACE)
+ param, content_id, desc, enc, size = body_fields
+ match(T_SPACE)
+ env = envelope
+ match(T_SPACE)
+ b = body
+ match(T_SPACE)
+ lines = number
+ md5, disposition, language, extension = body_ext_1part
+ return BodyTypeMessage.new(mtype, msubtype,
+ param, content_id,
+ desc, enc, size,
+ env, b, lines,
+ md5, disposition, language, extension)
+ end
+
+ def body_type_mpart
+ parts = []
+ while true
+ token = lookahead
+ if token.symbol == T_SPACE
+ shift_token
+ break
+ end
+ parts.push(body)
+ end
+ mtype = "MULTIPART"
+ msubtype = case_insensitive_string
+ param, disposition, language, extension = body_ext_mpart
+ return BodyTypeMultipart.new(mtype, msubtype, parts,
+ param, disposition, language,
+ extension)
+ end
+
+ def media_type
+ mtype = case_insensitive_string
+ match(T_SPACE)
+ msubtype = case_insensitive_string
+ return mtype, msubtype
+ end
+
+ def body_fields
+ param = body_fld_param
+ match(T_SPACE)
+ content_id = nstring
+ match(T_SPACE)
+ desc = nstring
+ match(T_SPACE)
+ enc = case_insensitive_string
+ match(T_SPACE)
+ size = number
+ return param, content_id, desc, enc, size
+ end
+
+ def body_fld_param
+ token = lookahead
+ if token.symbol == T_NIL
+ shift_token
+ return nil
+ end
+ match(T_LPAR)
+ param = {}
+ while true
+ token = lookahead
+ case token.symbol
+ when T_RPAR
+ shift_token
+ break
+ when T_SPACE
+ shift_token
+ end
+ name = case_insensitive_string
+ match(T_SPACE)
+ val = string
+ param[name] = val
+ end
+ return param
+ end
+
+ def body_ext_1part
+ token = lookahead
+ if token.symbol == T_SPACE
+ shift_token
+ else
+ return nil
+ end
+ md5 = nstring
+
+ token = lookahead
+ if token.symbol == T_SPACE
+ shift_token
+ else
+ return md5
+ end
+ disposition = body_fld_dsp
+
+ token = lookahead
+ if token.symbol == T_SPACE
+ shift_token
+ else
+ return md5, disposition
+ end
+ language = body_fld_lang
+
+ token = lookahead
+ if token.symbol == T_SPACE
+ shift_token
+ else
+ return md5, disposition, language
+ end
+
+ extension = body_extensions
+ return md5, disposition, language, extension
+ end
+
+ def body_ext_mpart
+ token = lookahead
+ if token.symbol == T_SPACE
+ shift_token
+ else
+ return nil
+ end
+ param = body_fld_param
+
+ token = lookahead
+ if token.symbol == T_SPACE
+ shift_token
+ else
+ return param
+ end
+ disposition = body_fld_dsp
+ match(T_SPACE)
+ language = body_fld_lang
+
+ token = lookahead
+ if token.symbol == T_SPACE
+ shift_token
+ else
+ return param, disposition, language
+ end
+
+ extension = body_extensions
+ return param, disposition, language, extension
+ end
+
+ def body_fld_dsp
+ token = lookahead
+ if token.symbol == T_NIL
+ shift_token
+ return nil
+ end
+ match(T_LPAR)
+ dsp_type = case_insensitive_string
+ match(T_SPACE)
+ param = body_fld_param
+ match(T_RPAR)
+ return ContentDisposition.new(dsp_type, param)
+ end
+
+ def body_fld_lang
+ token = lookahead
+ if token.symbol == T_LPAR
+ shift_token
+ result = []
+ while true
+ token = lookahead
+ case token.symbol
+ when T_RPAR
+ shift_token
+ return result
+ when T_SPACE
+ shift_token
+ end
+ result.push(case_insensitive_string)
+ end
+ else
+ lang = nstring
+ if lang
+ return lang.upcase
+ else
+ return lang
+ end
+ end
+ end
+
+ def body_extensions
+ result = []
+ while true
+ token = lookahead
+ case token.symbol
+ when T_RPAR
+ return result
+ when T_SPACE
+ shift_token
+ end
+ result.push(body_extension)
+ end
+ end
+
+ def body_extension
+ token = lookahead
+ case token.symbol
+ when T_LPAR
+ shift_token
+ result = body_extensions
+ match(T_RPAR)
+ return result
+ when T_NUMBER
+ return number
+ else
+ return nstring
+ end
+ end
+
+ def section
+ str = ""
+ token = match(T_LBRA)
+ str.concat(token.value)
+ token = match(T_ATOM, T_NUMBER, T_RBRA)
+ if token.symbol == T_RBRA
+ str.concat(token.value)
+ return str
+ end
+ str.concat(token.value)
+ token = lookahead
+ if token.symbol == T_SPACE
+ shift_token
+ str.concat(token.value)
+ token = match(T_LPAR)
+ str.concat(token.value)
+ while true
+ token = lookahead
+ case token.symbol
+ when T_RPAR
+ str.concat(token.value)
+ shift_token
+ break
+ when T_SPACE
+ shift_token
+ str.concat(token.value)
+ end
+ str.concat(format_string(astring))
+ end
+ end
+ token = match(T_RBRA)
+ str.concat(token.value)
+ return str
+ end
+
+ def format_string(str)
+ case str
+ when ""
+ return '""'
+ when /[\x80-\xff\r\n]/n
+ # literal
+ return "{" + str.length.to_s + "}" + CRLF + str
+ when /[(){ \x00-\x1f\x7f%*"\\]/n
+ # quoted string
+ return '"' + str.gsub(/["\\]/n, "\\\\\\&") + '"'
+ else
+ # atom
+ return str
+ end
+ end
+
+ def uid_data
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ return name, number
+ end
+
+ def text_response
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ @lex_state = EXPR_TEXT
+ token = match(T_TEXT)
+ @lex_state = EXPR_BEG
+ return UntaggedResponse.new(name, token.value)
+ end
+
+ def flags_response
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ return UntaggedResponse.new(name, flag_list, @str)
+ end
+
+ def list_response
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ return UntaggedResponse.new(name, mailbox_list, @str)
+ end
+
+ def mailbox_list
+ attr = flag_list
+ match(T_SPACE)
+ token = match(T_QUOTED, T_NIL)
+ if token.symbol == T_NIL
+ delim = nil
+ else
+ delim = token.value
+ end
+ match(T_SPACE)
+ name = astring
+ return MailboxList.new(attr, delim, name)
+ end
+
+ def getquota_response
+ # If quota never established, get back
+ # `NO Quota root does not exist'.
+ # If quota removed, get `()' after the
+ # folder spec with no mention of `STORAGE'.
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ mailbox = astring
+ match(T_SPACE)
+ match(T_LPAR)
+ token = lookahead
+ case token.symbol
+ when T_RPAR
+ shift_token
+ data = MailboxQuota.new(mailbox, nil, nil)
+ return UntaggedResponse.new(name, data, @str)
+ when T_ATOM
+ shift_token
+ match(T_SPACE)
+ token = match(T_NUMBER)
+ usage = token.value
+ match(T_SPACE)
+ token = match(T_NUMBER)
+ quota = token.value
+ match(T_RPAR)
+ data = MailboxQuota.new(mailbox, usage, quota)
+ return UntaggedResponse.new(name, data, @str)
+ else
+ parse_error("unexpected token %s", token.symbol)
+ end
+ end
+
+ def getquotaroot_response
+ # Similar to getquota, but only admin can use getquota.
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ mailbox = astring
+ quotaroots = []
+ while true
+ token = lookahead
+ break unless token.symbol == T_SPACE
+ shift_token
+ quotaroots.push(astring)
+ end
+ data = MailboxQuotaRoot.new(mailbox, quotaroots)
+ return UntaggedResponse.new(name, data, @str)
+ end
+
+ def getacl_response
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ mailbox = astring
+ data = []
+ token = lookahead
+ if token.symbol == T_SPACE
+ shift_token
+ while true
+ token = lookahead
+ case token.symbol
+ when T_CRLF
+ break
+ when T_SPACE
+ shift_token
+ end
+ user = astring
+ match(T_SPACE)
+ rights = astring
+ ##XXX data.push([user, rights])
+ data.push(MailboxACLItem.new(user, rights))
+ end
+ end
+ return UntaggedResponse.new(name, data, @str)
+ end
+
+ def search_response
+ token = match(T_ATOM)
+ name = token.value.upcase
+ token = lookahead
+ if token.symbol == T_SPACE
+ shift_token
+ data = []
+ while true
+ token = lookahead
+ case token.symbol
+ when T_CRLF
+ break
+ when T_SPACE
+ shift_token
+ end
+ data.push(number)
+ end
+ else
+ data = []
+ end
+ return UntaggedResponse.new(name, data, @str)
+ end
+
+ def thread_response
+ token = match(T_ATOM)
+ name = token.value.upcase
+ token = lookahead
+
+ if token.symbol == T_SPACE
+ threads = []
+
+ while true
+ shift_token
+ token = lookahead
+
+ case token.symbol
+ when T_LPAR
+ threads << thread_branch(token)
+ when T_CRLF
+ break
+ end
+ end
+ else
+ # no member
+ threads = []
+ end
+
+ return UntaggedResponse.new(name, threads, @str)
+ end
+
+ def thread_branch(token)
+ rootmember = nil
+ lastmember = nil
+
+ while true
+ shift_token # ignore first T_LPAR
+ token = lookahead
+
+ case token.symbol
+ when T_NUMBER
+ # new member
+ newmember = ThreadMember.new(number, [])
+ if rootmember.nil?
+ rootmember = newmember
+ else
+ lastmember.children << newmember
+ end
+ lastmember = newmember
+ when T_SPACE
+ # do nothing
+ when T_LPAR
+ if rootmember.nil?
+ # dummy member
+ lastmember = rootmember = ThreadMember.new(nil, [])
+ end
+
+ lastmember.children << thread_branch(token)
+ when T_RPAR
+ break
+ end
+ end
+
+ return rootmember
+ end
+
+ def status_response
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ mailbox = astring
+ match(T_SPACE)
+ match(T_LPAR)
+ attr = {}
+ while true
+ token = lookahead
+ case token.symbol
+ when T_RPAR
+ shift_token
+ break
+ when T_SPACE
+ shift_token
+ end
+ token = match(T_ATOM)
+ key = token.value.upcase
+ match(T_SPACE)
+ val = number
+ attr[key] = val
+ end
+ data = StatusData.new(mailbox, attr)
+ return UntaggedResponse.new(name, data, @str)
+ end
+
+ def capability_response
+ token = match(T_ATOM)
+ name = token.value.upcase
+ match(T_SPACE)
+ data = []
+ while true
+ token = lookahead
+ case token.symbol
+ when T_CRLF
+ break
+ when T_SPACE
+ shift_token
+ end
+ data.push(atom.upcase)
+ end
+ return UntaggedResponse.new(name, data, @str)
+ end
+
+ def resp_text
+ @lex_state = EXPR_RTEXT
+ token = lookahead
+ if token.symbol == T_LBRA
+ code = resp_text_code
+ else
+ code = nil
+ end
+ token = match(T_TEXT)
+ @lex_state = EXPR_BEG
+ return ResponseText.new(code, token.value)
+ end
+
+ def resp_text_code
+ @lex_state = EXPR_BEG
+ match(T_LBRA)
+ token = match(T_ATOM)
+ name = token.value.upcase
+ case name
+ when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ)\z/n
+ result = ResponseCode.new(name, nil)
+ when /\A(?:PERMANENTFLAGS)\z/n
+ match(T_SPACE)
+ result = ResponseCode.new(name, flag_list)
+ when /\A(?:UIDVALIDITY|UIDNEXT|UNSEEN)\z/n
+ match(T_SPACE)
+ result = ResponseCode.new(name, number)
+ else
+ match(T_SPACE)
+ @lex_state = EXPR_CTEXT
+ token = match(T_TEXT)
+ @lex_state = EXPR_BEG
+ result = ResponseCode.new(name, token.value)
+ end
+ match(T_RBRA)
+ @lex_state = EXPR_RTEXT
+ return result
+ end
+
+ def address_list
+ token = lookahead
+ if token.symbol == T_NIL
+ shift_token
+ return nil
+ else
+ result = []
+ match(T_LPAR)
+ while true
+ token = lookahead
+ case token.symbol
+ when T_RPAR
+ shift_token
+ break
+ when T_SPACE
+ shift_token
+ end
+ result.push(address)
+ end
+ return result
+ end
+ end
+
+ ADDRESS_REGEXP = /\G\
+(?# 1: NAME )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
+(?# 2: ROUTE )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
+(?# 3: MAILBOX )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
+(?# 4: HOST )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)")\
+\)/ni
+
+ def address
+ match(T_LPAR)
+ if @str.index(ADDRESS_REGEXP, @pos)
+ # address does not include literal.
+ @pos = $~.end(0)
+ name = $1
+ route = $2
+ mailbox = $3
+ host = $4
+ for s in [name, route, mailbox, host]
+ if s
+ s.gsub!(/\\(["\\])/n, "\\1")
+ end
+ end
+ else
+ name = nstring
+ match(T_SPACE)
+ route = nstring
+ match(T_SPACE)
+ mailbox = nstring
+ match(T_SPACE)
+ host = nstring
+ match(T_RPAR)
+ end
+ return Address.new(name, route, mailbox, host)
+ end
+
+# def flag_list
+# result = []
+# match(T_LPAR)
+# while true
+# token = lookahead
+# case token.symbol
+# when T_RPAR
+# shift_token
+# break
+# when T_SPACE
+# shift_token
+# end
+# result.push(flag)
+# end
+# return result
+# end
+
+# def flag
+# token = lookahead
+# if token.symbol == T_BSLASH
+# shift_token
+# token = lookahead
+# if token.symbol == T_STAR
+# shift_token
+# return token.value.intern
+# else
+# return atom.intern
+# end
+# else
+# return atom
+# end
+# end
+
+ FLAG_REGEXP = /\
+(?# FLAG )\\([^\x80-\xff(){ \x00-\x1f\x7f%"\\]+)|\
+(?# ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\]+)/n
+
+ def flag_list
+ if @str.index(/\(([^)]*)\)/ni, @pos)
+ @pos = $~.end(0)
+ return $1.scan(FLAG_REGEXP).collect { |flag, atom|
+ atom || flag.capitalize.intern
+ }
+ else
+ parse_error("invalid flag list")
+ end
+ end
+
+ def nstring
+ token = lookahead
+ if token.symbol == T_NIL
+ shift_token
+ return nil
+ else
+ return string
+ end
+ end
+
+ def astring
+ token = lookahead
+ if string_token?(token)
+ return string
+ else
+ return atom
+ end
+ end
+
+ def string
+ token = lookahead
+ if token.symbol == T_NIL
+ shift_token
+ return nil
+ end
+ token = match(T_QUOTED, T_LITERAL)
+ return token.value
+ end
+
+ STRING_TOKENS = [T_QUOTED, T_LITERAL, T_NIL]
+
+ def string_token?(token)
+ return STRING_TOKENS.include?(token.symbol)
+ end
+
+ def case_insensitive_string
+ token = lookahead
+ if token.symbol == T_NIL
+ shift_token
+ return nil
+ end
+ token = match(T_QUOTED, T_LITERAL)
+ return token.value.upcase
+ end
+
+ def atom
+ result = ""
+ while true
+ token = lookahead
+ if atom_token?(token)
+ result.concat(token.value)
+ shift_token
+ else
+ if result.empty?
+ parse_error("unexpected token %s", token.symbol)
+ else
+ return result
+ end
+ end
+ end
+ end
+
+ ATOM_TOKENS = [
+ T_ATOM,
+ T_NUMBER,
+ T_NIL,
+ T_LBRA,
+ T_RBRA,
+ T_PLUS
+ ]
+
+ def atom_token?(token)
+ return ATOM_TOKENS.include?(token.symbol)
+ end
+
+ def number
+ token = lookahead
+ if token.symbol == T_NIL
+ shift_token
+ return nil
+ end
+ token = match(T_NUMBER)
+ return token.value.to_i
+ end
+
+ def nil_atom
+ match(T_NIL)
+ return nil
+ end
+
+ def match(*args)
+ token = lookahead
+ unless args.include?(token.symbol)
+ parse_error('unexpected token %s (expected %s)',
+ token.symbol.id2name,
+ args.collect {|i| i.id2name}.join(" or "))
+ end
+ shift_token
+ return token
+ end
+
+ def lookahead
+ unless @token
+ @token = next_token
+ end
+ return @token
+ end
+
+ def shift_token
+ @token = nil
+ end
+
+ def next_token
+ case @lex_state
+ when EXPR_BEG
+ if @str.index(BEG_REGEXP, @pos)
+ @pos = $~.end(0)
+ if $1
+ return Token.new(T_SPACE, $+)
+ elsif $2
+ return Token.new(T_NIL, $+)
+ elsif $3
+ return Token.new(T_NUMBER, $+)
+ elsif $4
+ return Token.new(T_ATOM, $+)
+ elsif $5
+ return Token.new(T_QUOTED,
+ $+.gsub(/\\(["\\])/n, "\\1"))
+ elsif $6
+ return Token.new(T_LPAR, $+)
+ elsif $7
+ return Token.new(T_RPAR, $+)
+ elsif $8
+ return Token.new(T_BSLASH, $+)
+ elsif $9
+ return Token.new(T_STAR, $+)
+ elsif $10
+ return Token.new(T_LBRA, $+)
+ elsif $11
+ return Token.new(T_RBRA, $+)
+ elsif $12
+ len = $+.to_i
+ val = @str[@pos, len]
+ @pos += len
+ return Token.new(T_LITERAL, val)
+ elsif $13
+ return Token.new(T_PLUS, $+)
+ elsif $14
+ return Token.new(T_PERCENT, $+)
+ elsif $15
+ return Token.new(T_CRLF, $+)
+ elsif $16
+ return Token.new(T_EOF, $+)
+ else
+ parse_error("[Net::IMAP BUG] BEG_REGEXP is invalid")
+ end
+ else
+ @str.index(/\S*/n, @pos)
+ parse_error("unknown token - %s", $&.dump)
+ end
+ when EXPR_DATA
+ if @str.index(DATA_REGEXP, @pos)
+ @pos = $~.end(0)
+ if $1
+ return Token.new(T_SPACE, $+)
+ elsif $2
+ return Token.new(T_NIL, $+)
+ elsif $3
+ return Token.new(T_NUMBER, $+)
+ elsif $4
+ return Token.new(T_QUOTED,
+ $+.gsub(/\\(["\\])/n, "\\1"))
+ elsif $5
+ len = $+.to_i
+ val = @str[@pos, len]
+ @pos += len
+ return Token.new(T_LITERAL, val)
+ elsif $6
+ return Token.new(T_LPAR, $+)
+ elsif $7
+ return Token.new(T_RPAR, $+)
+ else
+ parse_error("[Net::IMAP BUG] DATA_REGEXP is invalid")
+ end
+ else
+ @str.index(/\S*/n, @pos)
+ parse_error("unknown token - %s", $&.dump)
+ end
+ when EXPR_TEXT
+ if @str.index(TEXT_REGEXP, @pos)
+ @pos = $~.end(0)
+ if $1
+ return Token.new(T_TEXT, $+)
+ else
+ parse_error("[Net::IMAP BUG] TEXT_REGEXP is invalid")
+ end
+ else
+ @str.index(/\S*/n, @pos)
+ parse_error("unknown token - %s", $&.dump)
+ end
+ when EXPR_RTEXT
+ if @str.index(RTEXT_REGEXP, @pos)
+ @pos = $~.end(0)
+ if $1
+ return Token.new(T_LBRA, $+)
+ elsif $2
+ return Token.new(T_TEXT, $+)
+ else
+ parse_error("[Net::IMAP BUG] RTEXT_REGEXP is invalid")
+ end
+ else
+ @str.index(/\S*/n, @pos)
+ parse_error("unknown token - %s", $&.dump)
+ end
+ when EXPR_CTEXT
+ if @str.index(CTEXT_REGEXP, @pos)
+ @pos = $~.end(0)
+ if $1
+ return Token.new(T_TEXT, $+)
+ else
+ parse_error("[Net::IMAP BUG] CTEXT_REGEXP is invalid")
+ end
+ else
+ @str.index(/\S*/n, @pos) #/
+ parse_error("unknown token - %s", $&.dump)
+ end
+ else
+ parse_error("illegal @lex_state - %s", @lex_state.inspect)
+ end
+ end
+
+ def parse_error(fmt, *args)
+ if IMAP.debug
+ $stderr.printf("@str: %s\n", @str.dump)
+ $stderr.printf("@pos: %d\n", @pos)
+ $stderr.printf("@lex_state: %s\n", @lex_state)
+ if @token
+ $stderr.printf("@token.symbol: %s\n", @token.symbol)
+ $stderr.printf("@token.value: %s\n", @token.value.inspect)
+ end
+ end
+ raise ResponseParseError, format(fmt, *args)
+ end
+ end
+
+ # Authenticator for the "LOGIN" authentication type. See
+ # #authenticate().
+ class LoginAuthenticator
+ def process(data)
+ case @state
+ when STATE_USER
+ @state = STATE_PASSWORD
+ return @user
+ when STATE_PASSWORD
+ return @password
+ end
+ end
+
+ private
+
+ STATE_USER = :USER
+ STATE_PASSWORD = :PASSWORD
+
+ def initialize(user, password)
+ @user = user
+ @password = password
+ @state = STATE_USER
+ end
+ end
+ add_authenticator "LOGIN", LoginAuthenticator
+
+ # Authenticator for the "CRAM-MD5" authentication type. See
+ # #authenticate().
+ class CramMD5Authenticator
+ def process(challenge)
+ digest = hmac_md5(challenge, @password)
+ return @user + " " + digest
+ end
+
+ private
+
+ def initialize(user, password)
+ @user = user
+ @password = password
+ end
+
+ def hmac_md5(text, key)
+ if key.length > 64
+ key = Digest::MD5.digest(key)
+ end
+
+ k_ipad = key + "\0" * (64 - key.length)
+ k_opad = key + "\0" * (64 - key.length)
+ for i in 0..63
+ k_ipad[i] ^= 0x36
+ k_opad[i] ^= 0x5c
+ end
+
+ digest = Digest::MD5.digest(k_ipad + text)
+
+ return Digest::MD5.hexdigest(k_opad + digest)
+ end
+ end
+ add_authenticator "CRAM-MD5", CramMD5Authenticator
+
+ # Superclass of IMAP errors.
+ class Error < StandardError
+ end
+
+ # Error raised when data is in the incorrect format.
+ class DataFormatError < Error
+ end
+
+ # Error raised when a response from the server is non-parseable.
+ class ResponseParseError < Error
+ end
+
+ # Superclass of all errors used to encapsulate "fail" responses
+ # from the server.
+ class ResponseError < Error
+ end
+
+ # Error raised upon a "NO" response from the server, indicating
+ # that the client command could not be completed successfully.
+ class NoResponseError < ResponseError
+ end
+
+ # Error raised upon a "BAD" response from the server, indicating
+ # that the client command violated the IMAP protocol, or an internal
+ # server failure has occurred.
+ class BadResponseError < ResponseError
+ end
+
+ # Error raised upon a "BYE" response from the server, indicating
+ # that the client is not being allowed to login, or has been timed
+ # out due to inactivity.
+ class ByeResponseError < ResponseError
+ end
+ end
+end
+
+if __FILE__ == $0
+ # :enddoc:
+ require "getoptlong"
+
+ $stdout.sync = true
+ $port = nil
+ $user = ENV["USER"] || ENV["LOGNAME"]
+ $auth = "login"
+ $ssl = false
+
+ def usage
+ $stderr.print <<EOF
+usage: #{$0} [options] <host>
+
+ --help print this message
+ --port=PORT specifies port
+ --user=USER specifies user
+ --auth=AUTH specifies auth type
+ --ssl use ssl
+EOF
+ end
+
+ def get_password
+ print "password: "
+ system("stty", "-echo")
+ begin
+ return gets.chop
+ ensure
+ system("stty", "echo")
+ print "\n"
+ end
+ end
+
+ def get_command
+ printf("%s@%s> ", $user, $host)
+ if line = gets
+ return line.strip.split(/\s+/)
+ else
+ return nil
+ end
+ end
+
+ parser = GetoptLong.new
+ parser.set_options(['--debug', GetoptLong::NO_ARGUMENT],
+ ['--help', GetoptLong::NO_ARGUMENT],
+ ['--port', GetoptLong::REQUIRED_ARGUMENT],
+ ['--user', GetoptLong::REQUIRED_ARGUMENT],
+ ['--auth', GetoptLong::REQUIRED_ARGUMENT],
+ ['--ssl', GetoptLong::NO_ARGUMENT])
+ begin
+ parser.each_option do |name, arg|
+ case name
+ when "--port"
+ $port = arg
+ when "--user"
+ $user = arg
+ when "--auth"
+ $auth = arg
+ when "--ssl"
+ $ssl = true
+ when "--debug"
+ Net::IMAP.debug = true
+ when "--help"
+ usage
+ exit(1)
+ end
+ end
+ rescue
+ usage
+ exit(1)
+ end
+
+ $host = ARGV.shift
+ unless $host
+ usage
+ exit(1)
+ end
+ $port ||= $ssl ? 993 : 143
+
+ imap = Net::IMAP.new($host, $port, $ssl)
+ begin
+ password = get_password
+ imap.authenticate($auth, $user, password)
+ while true
+ cmd, *args = get_command
+ break unless cmd
+ begin
+ case cmd
+ when "list"
+ for mbox in imap.list("", args[0] || "*")
+ if mbox.attr.include?(Net::IMAP::NOSELECT)
+ prefix = "!"
+ elsif mbox.attr.include?(Net::IMAP::MARKED)
+ prefix = "*"
+ else
+ prefix = " "
+ end
+ print prefix, mbox.name, "\n"
+ end
+ when "select"
+ imap.select(args[0] || "inbox")
+ print "ok\n"
+ when "close"
+ imap.close
+ print "ok\n"
+ when "summary"
+ unless messages = imap.responses["EXISTS"][-1]
+ puts "not selected"
+ next
+ end
+ if messages > 0
+ for data in imap.fetch(1..-1, ["ENVELOPE"])
+ print data.seqno, ": ", data.attr["ENVELOPE"].subject, "\n"
+ end
+ else
+ puts "no message"
+ end
+ when "fetch"
+ if args[0]
+ data = imap.fetch(args[0].to_i, ["RFC822.HEADER", "RFC822.TEXT"])[0]
+ puts data.attr["RFC822.HEADER"]
+ puts data.attr["RFC822.TEXT"]
+ else
+ puts "missing argument"
+ end
+ when "logout", "exit", "quit"
+ break
+ when "help", "?"
+ print <<EOF
+list [pattern] list mailboxes
+select [mailbox] select mailbox
+close close mailbox
+summary display summary
+fetch [msgno] display message
+logout logout
+help, ? display help message
+EOF
+ else
+ print "unknown command: ", cmd, "\n"
+ end
+ rescue Net::IMAP::Error
+ puts $!
+ end
+ end
+ ensure
+ imap.logout
+ imap.disconnect
+ end
+end
+
diff --git a/ruby_1_8_6/lib/net/pop.rb b/ruby_1_8_6/lib/net/pop.rb
new file mode 100644
index 0000000000..2ecbcbdf28
--- /dev/null
+++ b/ruby_1_8_6/lib/net/pop.rb
@@ -0,0 +1,879 @@
+# = net/pop.rb
+#
+# Copyright (c) 1999-2003 Yukihiro Matsumoto.
+#
+# Copyright (c) 1999-2003 Minero Aoki.
+#
+# Written & maintained by Minero Aoki <aamine@loveruby.net>.
+#
+# Documented by William Webber and Minero Aoki.
+#
+# This program is free software. You can re-distribute and/or
+# modify this program under the same terms as Ruby itself,
+# Ruby Distribute License or GNU General Public License.
+#
+# NOTE: You can find Japanese version of this document in
+# the doc/net directory of the standard ruby interpreter package.
+#
+# $Id$
+#
+# See Net::POP3 for documentation.
+#
+
+require 'net/protocol'
+require 'digest/md5'
+
+module Net
+
+ # Non-authentication POP3 protocol error
+ # (reply code "-ERR", except authentication).
+ class POPError < ProtocolError; end
+
+ # POP3 authentication error.
+ class POPAuthenticationError < ProtoAuthError; end
+
+ # Unexpected response from the server.
+ class POPBadResponse < POPError; end
+
+ #
+ # = Net::POP3
+ #
+ # == What is This Library?
+ #
+ # This library provides functionality for retrieving
+ # email via POP3, the Post Office Protocol version 3. For details
+ # of POP3, see [RFC1939] (http://www.ietf.org/rfc/rfc1939.txt).
+ #
+ # == Examples
+ #
+ # === Retrieving Messages
+ #
+ # This example retrieves messages from the server and deletes them
+ # on the server.
+ #
+ # Messages are written to files named 'inbox/1', 'inbox/2', ....
+ # Replace 'pop.example.com' with your POP3 server address, and
+ # 'YourAccount' and 'YourPassword' with the appropriate account
+ # details.
+ #
+ # require 'net/pop'
+ #
+ # pop = Net::POP3.new('pop.example.com')
+ # pop.start('YourAccount', 'YourPassword') # (1)
+ # if pop.mails.empty?
+ # puts 'No mail.'
+ # else
+ # i = 0
+ # pop.each_mail do |m| # or "pop.mails.each ..." # (2)
+ # File.open("inbox/#{i}", 'w') do |f|
+ # f.write m.pop
+ # end
+ # m.delete
+ # i += 1
+ # end
+ # puts "#{pop.mails.size} mails popped."
+ # end
+ # pop.finish # (3)
+ #
+ # 1. Call Net::POP3#start and start POP session.
+ # 2. Access messages by using POP3#each_mail and/or POP3#mails.
+ # 3. Close POP session by calling POP3#finish or use the block form of #start.
+ #
+ # === Shortened Code
+ #
+ # The example above is very verbose. You can shorten the code by using
+ # some utility methods. First, the block form of Net::POP3.start can
+ # be used instead of POP3.new, POP3#start and POP3#finish.
+ #
+ # require 'net/pop'
+ #
+ # Net::POP3.start('pop.example.com', 110,
+ # 'YourAccount', 'YourPassword') do |pop|
+ # if pop.mails.empty?
+ # puts 'No mail.'
+ # else
+ # i = 0
+ # pop.each_mail do |m| # or "pop.mails.each ..."
+ # File.open("inbox/#{i}", 'w') do |f|
+ # f.write m.pop
+ # end
+ # m.delete
+ # i += 1
+ # end
+ # puts "#{pop.mails.size} mails popped."
+ # end
+ # end
+ #
+ # POP3#delete_all is an alternative for #each_mail and #delete.
+ #
+ # require 'net/pop'
+ #
+ # Net::POP3.start('pop.example.com', 110,
+ # 'YourAccount', 'YourPassword') do |pop|
+ # if pop.mails.empty?
+ # puts 'No mail.'
+ # else
+ # i = 1
+ # pop.delete_all do |m|
+ # File.open("inbox/#{i}", 'w') do |f|
+ # f.write m.pop
+ # end
+ # i += 1
+ # end
+ # end
+ # end
+ #
+ # And here is an even shorter example.
+ #
+ # require 'net/pop'
+ #
+ # i = 0
+ # Net::POP3.delete_all('pop.example.com', 110,
+ # 'YourAccount', 'YourPassword') do |m|
+ # File.open("inbox/#{i}", 'w') do |f|
+ # f.write m.pop
+ # end
+ # i += 1
+ # end
+ #
+ # === Memory Space Issues
+ #
+ # All the examples above get each message as one big string.
+ # This example avoids this.
+ #
+ # require 'net/pop'
+ #
+ # i = 1
+ # Net::POP3.delete_all('pop.example.com', 110,
+ # 'YourAccount', 'YourPassword') do |m|
+ # File.open("inbox/#{i}", 'w') do |f|
+ # m.pop do |chunk| # get a message little by little.
+ # f.write chunk
+ # end
+ # i += 1
+ # end
+ # end
+ #
+ # === Using APOP
+ #
+ # The net/pop library supports APOP authentication.
+ # To use APOP, use the Net::APOP class instead of the Net::POP3 class.
+ # You can use the utility method, Net::POP3.APOP(). For example:
+ #
+ # require 'net/pop'
+ #
+ # # Use APOP authentication if $isapop == true
+ # pop = Net::POP3.APOP($is_apop).new('apop.example.com', 110)
+ # pop.start(YourAccount', 'YourPassword') do |pop|
+ # # Rest of the code is the same.
+ # end
+ #
+ # === Fetch Only Selected Mail Using 'UIDL' POP Command
+ #
+ # If your POP server provides UIDL functionality,
+ # you can grab only selected mails from the POP server.
+ # e.g.
+ #
+ # def need_pop?( id )
+ # # determine if we need pop this mail...
+ # end
+ #
+ # Net::POP3.start('pop.example.com', 110,
+ # 'Your account', 'Your password') do |pop|
+ # pop.mails.select { |m| need_pop?(m.unique_id) }.each do |m|
+ # do_something(m.pop)
+ # end
+ # end
+ #
+ # The POPMail#unique_id() method returns the unique-id of the message as a
+ # String. Normally the unique-id is a hash of the message.
+ #
+ class POP3 < Protocol
+
+ Revision = %q$Revision$.split[1]
+
+ #
+ # Class Parameters
+ #
+
+ # The default port for POP3 connections, port 110
+ def POP3.default_port
+ 110
+ end
+
+ def POP3.socket_type #:nodoc: obsolete
+ Net::InternetMessageIO
+ end
+
+ #
+ # Utilities
+ #
+
+ # Returns the APOP class if +isapop+ is true; otherwise, returns
+ # the POP class. For example:
+ #
+ # # Example 1
+ # pop = Net::POP3::APOP($is_apop).new(addr, port)
+ #
+ # # Example 2
+ # Net::POP3::APOP($is_apop).start(addr, port) do |pop|
+ # ....
+ # end
+ #
+ def POP3.APOP( isapop )
+ isapop ? APOP : POP3
+ end
+
+ # Starts a POP3 session and iterates over each POPMail object,
+ # yielding it to the +block+.
+ # This method is equivalent to:
+ #
+ # Net::POP3.start(address, port, account, password) do |pop|
+ # pop.each_mail do |m|
+ # yield m
+ # end
+ # end
+ #
+ # This method raises a POPAuthenticationError if authentication fails.
+ #
+ # === Example
+ #
+ # Net::POP3.foreach('pop.example.com', 110,
+ # 'YourAccount', 'YourPassword') do |m|
+ # file.write m.pop
+ # m.delete if $DELETE
+ # end
+ #
+ def POP3.foreach( address, port = nil,
+ account = nil, password = nil,
+ isapop = false, &block ) # :yields: message
+ start(address, port, account, password, isapop) {|pop|
+ pop.each_mail(&block)
+ }
+ end
+
+ # Starts a POP3 session and deletes all messages on the server.
+ # If a block is given, each POPMail object is yielded to it before
+ # being deleted.
+ #
+ # This method raises a POPAuthenticationError if authentication fails.
+ #
+ # === Example
+ #
+ # Net::POP3.delete_all('pop.example.com', 110,
+ # 'YourAccount', 'YourPassword') do |m|
+ # file.write m.pop
+ # end
+ #
+ def POP3.delete_all( address, port = nil,
+ account = nil, password = nil,
+ isapop = false, &block )
+ start(address, port, account, password, isapop) {|pop|
+ pop.delete_all(&block)
+ }
+ end
+
+ # Opens a POP3 session, attempts authentication, and quits.
+ #
+ # This method raises POPAuthenticationError if authentication fails.
+ #
+ # === Example: normal POP3
+ #
+ # Net::POP3.auth_only('pop.example.com', 110,
+ # 'YourAccount', 'YourPassword')
+ #
+ # === Example: APOP
+ #
+ # Net::POP3.auth_only('pop.example.com', 110,
+ # 'YourAccount', 'YourPassword', true)
+ #
+ def POP3.auth_only( address, port = nil,
+ account = nil, password = nil,
+ isapop = false )
+ new(address, port, isapop).auth_only account, password
+ end
+
+ # Starts a pop3 session, attempts authentication, and quits.
+ # This method must not be called while POP3 session is opened.
+ # This method raises POPAuthenticationError if authentication fails.
+ def auth_only( account, password )
+ raise IOError, 'opening previously opened POP session' if started?
+ start(account, password) {
+ ;
+ }
+ end
+
+ #
+ # Session management
+ #
+
+ # Creates a new POP3 object and open the connection. Equivalent to
+ #
+ # Net::POP3.new(address, port, isapop).start(account, password)
+ #
+ # If +block+ is provided, yields the newly-opened POP3 object to it,
+ # and automatically closes it at the end of the session.
+ #
+ # === Example
+ #
+ # Net::POP3.start(addr, port, account, password) do |pop|
+ # pop.each_mail do |m|
+ # file.write m.pop
+ # m.delete
+ # end
+ # end
+ #
+ def POP3.start( address, port = nil,
+ account = nil, password = nil,
+ isapop = false, &block ) # :yield: pop
+ new(address, port, isapop).start(account, password, &block)
+ end
+
+ # Creates a new POP3 object.
+ #
+ # +address+ is the hostname or ip address of your POP3 server.
+ #
+ # The optional +port+ is the port to connect to; it defaults to 110.
+ #
+ # The optional +isapop+ specifies whether this connection is going
+ # to use APOP authentication; it defaults to +false+.
+ #
+ # This method does *not* open the TCP connection.
+ def initialize( addr, port = nil, isapop = false )
+ @address = addr
+ @port = port || self.class.default_port
+ @apop = isapop
+
+ @command = nil
+ @socket = nil
+ @started = false
+ @open_timeout = 30
+ @read_timeout = 60
+ @debug_output = nil
+
+ @mails = nil
+ @n_mails = nil
+ @n_bytes = nil
+ end
+
+ # Does this instance use APOP authentication?
+ def apop?
+ @apop
+ end
+
+ # Provide human-readable stringification of class state.
+ def inspect
+ "#<#{self.class} #{@address}:#{@port} open=#{@started}>"
+ end
+
+ # *WARNING*: This method causes a serious security hole.
+ # Use this method only for debugging.
+ #
+ # Set an output stream for debugging.
+ #
+ # === Example
+ #
+ # pop = Net::POP.new(addr, port)
+ # pop.set_debug_output $stderr
+ # pop.start(account, passwd) do |pop|
+ # ....
+ # end
+ #
+ def set_debug_output( arg )
+ @debug_output = arg
+ end
+
+ # The address to connect to.
+ attr_reader :address
+
+ # The port number to connect to.
+ attr_reader :port
+
+ # Seconds to wait until a connection is opened.
+ # If the POP3 object cannot open a connection within this time,
+ # it raises a TimeoutError exception.
+ attr_accessor :open_timeout
+
+ # Seconds to wait until reading one block (by one read(1) call).
+ # If the POP3 object cannot complete a read() within this time,
+ # it raises a TimeoutError exception.
+ attr_reader :read_timeout
+
+ # Set the read timeout.
+ def read_timeout=( sec )
+ @command.socket.read_timeout = sec if @command
+ @read_timeout = sec
+ end
+
+ # +true+ if the POP3 session has started.
+ def started?
+ @started
+ end
+
+ alias active? started? #:nodoc: obsolete
+
+ # Starts a POP3 session.
+ #
+ # When called with block, gives a POP3 object to the block and
+ # closes the session after block call finishes.
+ #
+ # This method raises a POPAuthenticationError if authentication fails.
+ def start( account, password ) # :yield: pop
+ raise IOError, 'POP session already started' if @started
+
+ if block_given?
+ begin
+ do_start account, password
+ return yield(self)
+ ensure
+ do_finish
+ end
+ else
+ do_start account, password
+ return self
+ end
+ end
+
+ def do_start( account, password )
+ @socket = self.class.socket_type.old_open(@address, @port,
+ @open_timeout, @read_timeout, @debug_output)
+ on_connect
+ @command = POP3Command.new(@socket)
+ if apop?
+ @command.apop account, password
+ else
+ @command.auth account, password
+ end
+ @started = true
+ ensure
+ do_finish if not @started
+ end
+ private :do_start
+
+ def on_connect
+ end
+ private :on_connect
+
+ # Finishes a POP3 session and closes TCP connection.
+ def finish
+ raise IOError, 'POP session not yet started' unless started?
+ do_finish
+ end
+
+ def do_finish
+ @mails = nil
+ @command.quit if @command
+ ensure
+ @started = false
+ @command = nil
+ @socket.close if @socket and not @socket.closed?
+ @socket = nil
+ end
+ private :do_finish
+
+ def command
+ raise IOError, 'POP session not opened yet' \
+ if not @socket or @socket.closed?
+ @command
+ end
+ private :command
+
+ #
+ # POP protocol wrapper
+ #
+
+ # Returns the number of messages on the POP server.
+ def n_mails
+ return @n_mails if @n_mails
+ @n_mails, @n_bytes = command().stat
+ @n_mails
+ end
+
+ # Returns the total size in bytes of all the messages on the POP server.
+ def n_bytes
+ return @n_bytes if @n_bytes
+ @n_mails, @n_bytes = command().stat
+ @n_bytes
+ end
+
+ # Returns an array of Net::POPMail objects, representing all the
+ # messages on the server. This array is renewed when the session
+ # restarts; otherwise, it is fetched from the server the first time
+ # this method is called (directly or indirectly) and cached.
+ #
+ # This method raises a POPError if an error occurs.
+ def mails
+ return @mails.dup if @mails
+ if n_mails() == 0
+ # some popd raises error for LIST on the empty mailbox.
+ @mails = []
+ return []
+ end
+
+ @mails = command().list.map {|num, size|
+ POPMail.new(num, size, self, command())
+ }
+ @mails.dup
+ end
+
+ # Yields each message to the passed-in block in turn.
+ # Equivalent to:
+ #
+ # pop3.mails.each do |popmail|
+ # ....
+ # end
+ #
+ # This method raises a POPError if an error occurs.
+ def each_mail( &block ) # :yield: message
+ mails().each(&block)
+ end
+
+ alias each each_mail
+
+ # Deletes all messages on the server.
+ #
+ # If called with a block, yields each message in turn before deleting it.
+ #
+ # === Example
+ #
+ # n = 1
+ # pop.delete_all do |m|
+ # File.open("inbox/#{n}") do |f|
+ # f.write m.pop
+ # end
+ # n += 1
+ # end
+ #
+ # This method raises a POPError if an error occurs.
+ #
+ def delete_all # :yield: message
+ mails().each do |m|
+ yield m if block_given?
+ m.delete unless m.deleted?
+ end
+ end
+
+ # Resets the session. This clears all "deleted" marks from messages.
+ #
+ # This method raises a POPError if an error occurs.
+ def reset
+ command().rset
+ mails().each do |m|
+ m.instance_eval {
+ @deleted = false
+ }
+ end
+ end
+
+ def set_all_uids #:nodoc: internal use only (called from POPMail#uidl)
+ command().uidl.each do |num, uid|
+ @mails.find {|m| m.number == num }.uid = uid
+ end
+ end
+
+ end # class POP3
+
+ # class aliases
+ POP = POP3
+ POPSession = POP3
+ POP3Session = POP3
+
+ #
+ # This class is equivalent to POP3, except that it uses APOP authentication.
+ #
+ class APOP < POP3
+ # Always returns true.
+ def apop?
+ true
+ end
+ end
+
+ # class aliases
+ APOPSession = APOP
+
+ #
+ # This class represents a message which exists on the POP server.
+ # Instances of this class are created by the POP3 class; they should
+ # not be directly created by the user.
+ #
+ class POPMail
+
+ def initialize( num, len, pop, cmd ) #:nodoc:
+ @number = num
+ @length = len
+ @pop = pop
+ @command = cmd
+ @deleted = false
+ @uid = nil
+ end
+
+ # The sequence number of the message on the server.
+ attr_reader :number
+
+ # The length of the message in octets.
+ attr_reader :length
+ alias size length
+
+ # Provide human-readable stringification of class state.
+ def inspect
+ "#<#{self.class} #{@number}#{@deleted ? ' deleted' : ''}>"
+ end
+
+ #
+ # This method fetches the message. If called with a block, the
+ # message is yielded to the block one chunk at a time. If called
+ # without a block, the message is returned as a String. The optional
+ # +dest+ argument will be prepended to the returned String; this
+ # argument is essentially obsolete.
+ #
+ # === Example without block
+ #
+ # POP3.start('pop.example.com', 110,
+ # 'YourAccount, 'YourPassword') do |pop|
+ # n = 1
+ # pop.mails.each do |popmail|
+ # File.open("inbox/#{n}", 'w') do |f|
+ # f.write popmail.pop
+ # end
+ # popmail.delete
+ # n += 1
+ # end
+ # end
+ #
+ # === Example with block
+ #
+ # POP3.start('pop.example.com', 110,
+ # 'YourAccount, 'YourPassword') do |pop|
+ # n = 1
+ # pop.mails.each do |popmail|
+ # File.open("inbox/#{n}", 'w') do |f|
+ # popmail.pop do |chunk| ####
+ # f.write chunk
+ # end
+ # end
+ # n += 1
+ # end
+ # end
+ #
+ # This method raises a POPError if an error occurs.
+ #
+ def pop( dest = '', &block ) # :yield: message_chunk
+ if block_given?
+ @command.retr(@number, &block)
+ nil
+ else
+ @command.retr(@number) do |chunk|
+ dest << chunk
+ end
+ dest
+ end
+ end
+
+ alias all pop #:nodoc: obsolete
+ alias mail pop #:nodoc: obsolete
+
+ # Fetches the message header and +lines+ lines of body.
+ #
+ # The optional +dest+ argument is obsolete.
+ #
+ # This method raises a POPError if an error occurs.
+ def top( lines, dest = '' )
+ @command.top(@number, lines) do |chunk|
+ dest << chunk
+ end
+ dest
+ end
+
+ # Fetches the message header.
+ #
+ # The optional +dest+ argument is obsolete.
+ #
+ # This method raises a POPError if an error occurs.
+ def header( dest = '' )
+ top(0, dest)
+ end
+
+ # Marks a message for deletion on the server. Deletion does not
+ # actually occur until the end of the session; deletion may be
+ # cancelled for _all_ marked messages by calling POP3#reset().
+ #
+ # This method raises a POPError if an error occurs.
+ #
+ # === Example
+ #
+ # POP3.start('pop.example.com', 110,
+ # 'YourAccount, 'YourPassword') do |pop|
+ # n = 1
+ # pop.mails.each do |popmail|
+ # File.open("inbox/#{n}", 'w') do |f|
+ # f.write popmail.pop
+ # end
+ # popmail.delete ####
+ # n += 1
+ # end
+ # end
+ #
+ def delete
+ @command.dele @number
+ @deleted = true
+ end
+
+ alias delete! delete #:nodoc: obsolete
+
+ # True if the mail has been deleted.
+ def deleted?
+ @deleted
+ end
+
+ # Returns the unique-id of the message.
+ # Normally the unique-id is a hash string of the message.
+ #
+ # This method raises a POPError if an error occurs.
+ def unique_id
+ return @uid if @uid
+ @pop.set_all_uids
+ @uid
+ end
+
+ alias uidl unique_id
+
+ def uid=( uid ) #:nodoc: internal use only (used from POP3#set_all_uids)
+ @uid = uid
+ end
+
+ end # class POPMail
+
+
+ class POP3Command #:nodoc: internal use only
+
+ def initialize( sock )
+ @socket = sock
+ @error_occured = false
+ res = check_response(critical { recv_response() })
+ @apop_stamp = res.slice(/<.+>/)
+ end
+
+ def inspect
+ "#<#{self.class} socket=#{@socket}>"
+ end
+
+ def auth( account, password )
+ check_response_auth(critical {
+ check_response_auth(get_response('USER %s', account))
+ get_response('PASS %s', password)
+ })
+ end
+
+ def apop( account, password )
+ raise POPAuthenticationError, 'not APOP server; cannot login' \
+ unless @apop_stamp
+ check_response_auth(critical {
+ get_response('APOP %s %s',
+ account,
+ Digest::MD5.hexdigest(@apop_stamp + password))
+ })
+ end
+
+ def list
+ critical {
+ getok 'LIST'
+ list = []
+ @socket.each_list_item do |line|
+ m = /\A(\d+)[ \t]+(\d+)/.match(line) or
+ raise POPBadResponse, "bad response: #{line}"
+ list.push [m[1].to_i, m[2].to_i]
+ end
+ return list
+ }
+ end
+
+ def stat
+ res = check_response(critical { get_response('STAT') })
+ m = /\A\+OK\s+(\d+)\s+(\d+)/.match(res) or
+ raise POPBadResponse, "wrong response format: #{res}"
+ [m[1].to_i, m[2].to_i]
+ end
+
+ def rset
+ check_response(critical { get_response 'RSET' })
+ end
+
+ def top( num, lines = 0, &block )
+ critical {
+ getok('TOP %d %d', num, lines)
+ @socket.each_message_chunk(&block)
+ }
+ end
+
+ def retr( num, &block )
+ critical {
+ getok('RETR %d', num)
+ @socket.each_message_chunk(&block)
+ }
+ end
+
+ def dele( num )
+ check_response(critical { get_response('DELE %d', num) })
+ end
+
+ def uidl( num = nil )
+ if num
+ res = check_response(critical { get_response('UIDL %d', num) })
+ return res.split(/ /)[1]
+ else
+ critical {
+ getok('UIDL')
+ table = {}
+ @socket.each_list_item do |line|
+ num, uid = line.split
+ table[num.to_i] = uid
+ end
+ return table
+ }
+ end
+ end
+
+ def quit
+ check_response(critical { get_response('QUIT') })
+ end
+
+ private
+
+ def getok( fmt, *fargs )
+ @socket.writeline sprintf(fmt, *fargs)
+ check_response(recv_response())
+ end
+
+ def get_response( fmt, *fargs )
+ @socket.writeline sprintf(fmt, *fargs)
+ recv_response()
+ end
+
+ def recv_response
+ @socket.readline
+ end
+
+ def check_response( res )
+ raise POPError, res unless /\A\+OK/i === res
+ res
+ end
+
+ def check_response_auth( res )
+ raise POPAuthenticationError, res unless /\A\+OK/i === res
+ res
+ end
+
+ def critical
+ return '+OK dummy ok response' if @error_occured
+ begin
+ return yield()
+ rescue Exception
+ @error_occured = true
+ raise
+ end
+ end
+
+ end # class POP3Command
+
+end # module Net
+
diff --git a/ruby_1_8_6/lib/net/protocol.rb b/ruby_1_8_6/lib/net/protocol.rb
new file mode 100644
index 0000000000..d722fdcbd4
--- /dev/null
+++ b/ruby_1_8_6/lib/net/protocol.rb
@@ -0,0 +1,390 @@
+#
+# = net/protocol.rb
+#
+#--
+# Copyright (c) 1999-2005 Yukihiro Matsumoto
+# Copyright (c) 1999-2005 Minero Aoki
+#
+# written and maintained by Minero Aoki <aamine@loveruby.net>
+#
+# This program is free software. You can re-distribute and/or
+# modify this program under the same terms as Ruby itself,
+# Ruby Distribute License or GNU General Public License.
+#
+# $Id$
+#++
+#
+# WARNING: This file is going to remove.
+# Do not rely on the implementation written in this file.
+#
+
+require 'socket'
+require 'timeout'
+
+module Net # :nodoc:
+
+ class Protocol #:nodoc: internal use only
+ private
+ def Protocol.protocol_param(name, val)
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
+ def #{name}
+ #{val}
+ end
+ End
+ 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 ProtoRetriableError < ProtocolError; end
+ ProtocRetryError = ProtoRetriableError
+
+
+ class BufferedIO #:nodoc: internal use only
+ def initialize(io)
+ @io = io
+ @read_timeout = 60
+ @debug_output = nil
+ @rbuf = ''
+ end
+
+ attr_reader :io
+ attr_accessor :read_timeout
+ attr_accessor :debug_output
+
+ def inspect
+ "#<#{self.class} io=#{@io}>"
+ end
+
+ def closed?
+ @io.closed?
+ end
+
+ def close
+ @io.close
+ end
+
+ #
+ # Read
+ #
+
+ public
+
+ def read(len, dest = '', ignore_eof = false)
+ LOG "reading #{len} bytes..."
+ read_bytes = 0
+ begin
+ while read_bytes + @rbuf.size < len
+ dest << (s = rbuf_consume(@rbuf.size))
+ read_bytes += s.size
+ rbuf_fill
+ end
+ dest << (s = rbuf_consume(len - read_bytes))
+ read_bytes += s.size
+ rescue EOFError
+ raise unless ignore_eof
+ end
+ LOG "read #{read_bytes} bytes"
+ dest
+ end
+
+ def read_all(dest = '')
+ LOG 'reading all...'
+ read_bytes = 0
+ begin
+ while true
+ dest << (s = rbuf_consume(@rbuf.size))
+ read_bytes += s.size
+ rbuf_fill
+ end
+ rescue EOFError
+ ;
+ end
+ LOG "read #{read_bytes} bytes"
+ dest
+ end
+
+ def readuntil(terminator, ignore_eof = false)
+ begin
+ until idx = @rbuf.index(terminator)
+ rbuf_fill
+ end
+ return rbuf_consume(idx + terminator.size)
+ rescue EOFError
+ raise unless ignore_eof
+ return rbuf_consume(@rbuf.size)
+ end
+ end
+
+ def readline
+ readuntil("\n").chop
+ end
+
+ private
+
+ def rbuf_fill
+ timeout(@read_timeout) {
+ @rbuf << @io.sysread(1024)
+ }
+ end
+
+ def rbuf_consume(len)
+ s = @rbuf.slice!(0, len)
+ @debug_output << %Q[-> #{s.dump}\n] if @debug_output
+ s
+ end
+
+ #
+ # Write
+ #
+
+ public
+
+ def write(str)
+ writing {
+ write0 str
+ }
+ end
+
+ def writeline(str)
+ writing {
+ write0 str + "\r\n"
+ }
+ end
+
+ private
+
+ def writing
+ @written_bytes = 0
+ @debug_output << '<- ' if @debug_output
+ yield
+ @debug_output << "\n" if @debug_output
+ bytes = @written_bytes
+ @written_bytes = nil
+ bytes
+ end
+
+ def write0(str)
+ @debug_output << str.dump if @debug_output
+ len = @io.write(str)
+ @written_bytes += len
+ len
+ end
+
+ #
+ # Logging
+ #
+
+ private
+
+ def LOG_off
+ @save_debug_out = @debug_output
+ @debug_output = nil
+ end
+
+ def LOG_on
+ @debug_output = @save_debug_out
+ end
+
+ def LOG(msg)
+ return unless @debug_output
+ @debug_output << msg + "\n"
+ end
+ end
+
+
+ class InternetMessageIO < BufferedIO #:nodoc: internal use only
+ def InternetMessageIO.old_open(addr, port,
+ open_timeout = nil, read_timeout = nil, debug_output = nil)
+ debug_output << "opening connection to #{addr}...\n" if debug_output
+ s = timeout(open_timeout) { TCPsocket.new(addr, port) }
+ io = new(s)
+ io.read_timeout = read_timeout
+ io.debug_output = debug_output
+ io
+ end
+
+ def initialize(io)
+ super
+ @wbuf = nil
+ end
+
+ #
+ # Read
+ #
+
+ def each_message_chunk
+ LOG 'reading message...'
+ LOG_off()
+ read_bytes = 0
+ while (line = readuntil("\r\n")) != ".\r\n"
+ read_bytes += line.size
+ yield line.sub(/\A\./, '')
+ end
+ LOG_on()
+ LOG "read message (#{read_bytes} bytes)"
+ end
+
+ # *library private* (cannot handle 'break')
+ def each_list_item
+ while (str = readuntil("\r\n")) != ".\r\n"
+ yield str.chop
+ end
+ end
+
+ def write_message_0(src)
+ prev = @written_bytes
+ each_crlf_line(src) do |line|
+ write0 line.sub(/\A\./, '..')
+ end
+ @written_bytes - prev
+ end
+
+ #
+ # Write
+ #
+
+ def write_message(src)
+ LOG "writing message from #{src.class}"
+ LOG_off()
+ len = writing {
+ using_each_crlf_line {
+ write_message_0 src
+ }
+ }
+ LOG_on()
+ LOG "wrote #{len} bytes"
+ len
+ end
+
+ def write_message_by_block(&block)
+ LOG 'writing message from block'
+ LOG_off()
+ len = writing {
+ using_each_crlf_line {
+ begin
+ block.call(WriteAdapter.new(self, :write_message_0))
+ rescue LocalJumpError
+ # allow `break' from writer block
+ end
+ }
+ }
+ LOG_on()
+ LOG "wrote #{len} bytes"
+ len
+ end
+
+ private
+
+ def using_each_crlf_line
+ @wbuf = ''
+ yield
+ if not @wbuf.empty? # unterminated last line
+ write0 @wbuf.chomp + "\r\n"
+ elsif @written_bytes == 0 # empty src
+ write0 "\r\n"
+ end
+ write0 ".\r\n"
+ @wbuf = nil
+ end
+
+ def each_crlf_line(src)
+ buffer_filling(@wbuf, src) do
+ while line = @wbuf.slice!(/\A.*(?:\n|\r\n|\r(?!\z))/n)
+ yield line.chomp("\n") + "\r\n"
+ end
+ end
+ end
+
+ def buffer_filling(buf, src)
+ case src
+ when String # for speeding up.
+ 0.step(src.size - 1, 1024) do |i|
+ buf << src[i, 1024]
+ yield
+ end
+ when File # for speeding up.
+ while s = src.read(1024)
+ buf << s
+ yield
+ end
+ else # generic reader
+ src.each do |s|
+ buf << s
+ yield if buf.size > 1024
+ end
+ yield unless buf.empty?
+ end
+ end
+ end
+
+
+ #
+ # The writer adapter class
+ #
+ class WriteAdapter
+ def initialize(socket, method)
+ @socket = socket
+ @method_id = method
+ end
+
+ def inspect
+ "#<#{self.class} socket=#{@socket.inspect}>"
+ end
+
+ def write(str)
+ @socket.__send__(@method_id, str)
+ end
+
+ alias print write
+
+ def <<(str)
+ write str
+ self
+ end
+
+ def puts(str = '')
+ write str.chomp("\n") + "\n"
+ end
+
+ def printf(*args)
+ write sprintf(*args)
+ end
+ end
+
+
+ class ReadAdapter #:nodoc: internal use only
+ def initialize(block)
+ @block = block
+ end
+
+ def inspect
+ "#<#{self.class}>"
+ end
+
+ def <<(str)
+ call_block(str, &@block) if @block
+ end
+
+ private
+
+ # This method is needed because @block must be called by yield,
+ # not Proc#call. You can see difference when using `break' in
+ # the block.
+ def call_block(str)
+ yield str
+ end
+ end
+
+
+ module NetPrivate #:nodoc: obsolete
+ Socket = ::Net::InternetMessageIO
+ end
+
+end # module Net
diff --git a/ruby_1_8_6/lib/net/smtp.rb b/ruby_1_8_6/lib/net/smtp.rb
new file mode 100644
index 0000000000..dda9dab072
--- /dev/null
+++ b/ruby_1_8_6/lib/net/smtp.rb
@@ -0,0 +1,697 @@
+# = net/smtp.rb
+#
+# Copyright (c) 1999-2003 Yukihiro Matsumoto.
+#
+# Copyright (c) 1999-2003 Minero Aoki.
+#
+# Written & maintained by Minero Aoki <aamine@loveruby.net>.
+#
+# Documented by William Webber and Minero Aoki.
+#
+# This program is free software. You can re-distribute and/or
+# modify this program under the same terms as Ruby itself,
+# Ruby Distribute License or GNU General Public License.
+#
+# NOTE: You can find Japanese version of this document in
+# the doc/net directory of the standard ruby interpreter package.
+#
+# $Id$
+#
+# See Net::SMTP for documentation.
+#
+
+require 'net/protocol'
+require 'digest/md5'
+
+module Net
+
+ # Module mixed in to all SMTP error classes
+ module SMTPError
+ # This *class* is module for some reason.
+ # In ruby 1.9.x, this module becomes a class.
+ end
+
+ # Represents an SMTP authentication error.
+ class SMTPAuthenticationError < ProtoAuthError
+ include SMTPError
+ end
+
+ # Represents SMTP error code 420 or 450, a temporary error.
+ class SMTPServerBusy < ProtoServerError
+ include SMTPError
+ end
+
+ # Represents an SMTP command syntax error (error code 500)
+ class SMTPSyntaxError < ProtoSyntaxError
+ include SMTPError
+ end
+
+ # Represents a fatal SMTP error (error code 5xx, except for 500)
+ class SMTPFatalError < ProtoFatalError
+ include SMTPError
+ end
+
+ # Unexpected reply code returned from server.
+ class SMTPUnknownError < ProtoUnknownError
+ include SMTPError
+ end
+
+ #
+ # = Net::SMTP
+ #
+ # == What is This Library?
+ #
+ # This library provides functionality to send internet
+ # mail via SMTP, the Simple Mail Transfer Protocol. For details of
+ # SMTP itself, see [RFC2821] (http://www.ietf.org/rfc/rfc2821.txt).
+ #
+ # == What is This Library NOT?
+ #
+ # This library does NOT provide functions to compose internet mails.
+ # You must create them by yourself. If you want better mail support,
+ # try RubyMail or TMail. You can get both libraries from RAA.
+ # (http://www.ruby-lang.org/en/raa.html)
+ #
+ # FYI: the official documentation on internet mail is: [RFC2822] (http://www.ietf.org/rfc/rfc2822.txt).
+ #
+ # == Examples
+ #
+ # === Sending Messages
+ #
+ # You must open a connection to an SMTP server before sending messages.
+ # The first argument is the address of your SMTP server, and the second
+ # argument is the port number. Using SMTP.start with a block is the simplest
+ # way to do this. This way, the SMTP connection is closed automatically
+ # after the block is executed.
+ #
+ # require 'net/smtp'
+ # Net::SMTP.start('your.smtp.server', 25) do |smtp|
+ # # Use the SMTP object smtp only in this block.
+ # end
+ #
+ # Replace 'your.smtp.server' with your SMTP server. Normally
+ # your system manager or internet provider supplies a server
+ # for you.
+ #
+ # Then you can send messages.
+ #
+ # msgstr = <<END_OF_MESSAGE
+ # From: Your Name <your@mail.address>
+ # To: Destination Address <someone@example.com>
+ # Subject: test message
+ # Date: Sat, 23 Jun 2001 16:26:43 +0900
+ # Message-Id: <unique.message.id.string@example.com>
+ #
+ # This is a test message.
+ # END_OF_MESSAGE
+ #
+ # require 'net/smtp'
+ # Net::SMTP.start('your.smtp.server', 25) do |smtp|
+ # smtp.send_message msgstr,
+ # 'your@mail.address',
+ # 'his_addess@example.com'
+ # end
+ #
+ # === Closing the Session
+ #
+ # You MUST close the SMTP session after sending messages, by calling
+ # the #finish method:
+ #
+ # # using SMTP#finish
+ # smtp = Net::SMTP.start('your.smtp.server', 25)
+ # smtp.send_message msgstr, 'from@address', 'to@address'
+ # smtp.finish
+ #
+ # You can also use the block form of SMTP.start/SMTP#start. This closes
+ # the SMTP session automatically:
+ #
+ # # using block form of SMTP.start
+ # Net::SMTP.start('your.smtp.server', 25) do |smtp|
+ # smtp.send_message msgstr, 'from@address', 'to@address'
+ # end
+ #
+ # I strongly recommend this scheme. This form is simpler and more robust.
+ #
+ # === HELO domain
+ #
+ # In almost all situations, you must provide a third argument
+ # to SMTP.start/SMTP#start. This is the domain name which you are on
+ # (the host to send mail from). It is called the "HELO domain".
+ # The SMTP server will judge whether it should send or reject
+ # the SMTP session by inspecting the HELO domain.
+ #
+ # Net::SMTP.start('your.smtp.server', 25,
+ # 'mail.from.domain') { |smtp| ... }
+ #
+ # === SMTP Authentication
+ #
+ # The Net::SMTP class supports three authentication schemes;
+ # PLAIN, LOGIN and CRAM MD5. (SMTP Authentication: [RFC2554])
+ # To use SMTP authentication, pass extra arguments to
+ # SMTP.start/SMTP#start.
+ #
+ # # PLAIN
+ # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
+ # 'Your Account', 'Your Password', :plain)
+ # # LOGIN
+ # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
+ # 'Your Account', 'Your Password', :login)
+ #
+ # # CRAM MD5
+ # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
+ # 'Your Account', 'Your Password', :cram_md5)
+ #
+ class SMTP
+
+ Revision = %q$Revision$.split[1]
+
+ # The default SMTP port, port 25.
+ def SMTP.default_port
+ 25
+ end
+
+ #
+ # Creates a new Net::SMTP object.
+ #
+ # +address+ is the hostname or ip address of your SMTP
+ # server. +port+ is the port to connect to; it defaults to
+ # port 25.
+ #
+ # This method does not open the TCP connection. You can use
+ # SMTP.start instead of SMTP.new if you want to do everything
+ # at once. Otherwise, follow SMTP.new with SMTP#start.
+ #
+ def initialize( address, port = nil )
+ @address = address
+ @port = (port || SMTP.default_port)
+ @esmtp = true
+ @socket = nil
+ @started = false
+ @open_timeout = 30
+ @read_timeout = 60
+ @error_occured = false
+ @debug_output = nil
+ end
+
+ # Provide human-readable stringification of class state.
+ def inspect
+ "#<#{self.class} #{@address}:#{@port} started=#{@started}>"
+ end
+
+ # +true+ if the SMTP object uses ESMTP (which it does by default).
+ def esmtp?
+ @esmtp
+ end
+
+ #
+ # Set whether to use ESMTP or not. This should be done before
+ # calling #start. Note that if #start is called in ESMTP mode,
+ # and the connection fails due to a ProtocolError, the SMTP
+ # object will automatically switch to plain SMTP mode and
+ # retry (but not vice versa).
+ #
+ def esmtp=( bool )
+ @esmtp = bool
+ end
+
+ alias esmtp esmtp?
+
+ # The address of the SMTP server to connect to.
+ attr_reader :address
+
+ # The port number of the SMTP server to connect to.
+ attr_reader :port
+
+ # Seconds to wait while attempting to open a connection.
+ # If the connection cannot be opened within this time, a
+ # TimeoutError is raised.
+ attr_accessor :open_timeout
+
+ # Seconds to wait while reading one block (by one read(2) call).
+ # If the read(2) call does not complete within this time, a
+ # TimeoutError is raised.
+ attr_reader :read_timeout
+
+ # Set the number of seconds to wait until timing-out a read(2)
+ # call.
+ def read_timeout=( sec )
+ @socket.read_timeout = sec if @socket
+ @read_timeout = sec
+ end
+
+ #
+ # WARNING: This method causes serious security holes.
+ # Use this method for only debugging.
+ #
+ # Set an output stream for debug logging.
+ # You must call this before #start.
+ #
+ # # example
+ # smtp = Net::SMTP.new(addr, port)
+ # smtp.set_debug_output $stderr
+ # smtp.start do |smtp|
+ # ....
+ # end
+ #
+ def set_debug_output( arg )
+ @debug_output = arg
+ end
+
+ #
+ # SMTP session control
+ #
+
+ #
+ # Creates a new Net::SMTP object and connects to the server.
+ #
+ # This method is equivalent to:
+ #
+ # Net::SMTP.new(address, port).start(helo_domain, account, password, authtype)
+ #
+ # === Example
+ #
+ # Net::SMTP.start('your.smtp.server') do |smtp|
+ # smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
+ # end
+ #
+ # === Block Usage
+ #
+ # If called with a block, the newly-opened Net::SMTP object is yielded
+ # to the block, and automatically closed when the block finishes. If called
+ # without a block, the newly-opened Net::SMTP object is returned to
+ # the caller, and it is the caller's responsibility to close it when
+ # finished.
+ #
+ # === Parameters
+ #
+ # +address+ is the hostname or ip address of your smtp server.
+ #
+ # +port+ is the port to connect to; it defaults to port 25.
+ #
+ # +helo+ is the _HELO_ _domain_ provided by the client to the
+ # server (see overview comments); it defaults to 'localhost.localdomain'.
+ #
+ # The remaining arguments are used for SMTP authentication, if required
+ # or desired. +user+ is the account name; +secret+ is your password
+ # or other authentication token; and +authtype+ is the authentication
+ # type, one of :plain, :login, or :cram_md5. See the discussion of
+ # SMTP Authentication in the overview notes.
+ #
+ # === Errors
+ #
+ # This method may raise:
+ #
+ # * Net::SMTPAuthenticationError
+ # * Net::SMTPServerBusy
+ # * Net::SMTPSyntaxError
+ # * Net::SMTPFatalError
+ # * Net::SMTPUnknownError
+ # * IOError
+ # * TimeoutError
+ #
+ def SMTP.start( address, port = nil,
+ helo = 'localhost.localdomain',
+ user = nil, secret = nil, authtype = nil,
+ &block) # :yield: smtp
+ new(address, port).start(helo, user, secret, authtype, &block)
+ end
+
+ # +true+ if the SMTP session has been started.
+ def started?
+ @started
+ end
+
+ #
+ # Opens a TCP connection and starts the SMTP session.
+ #
+ # === Parameters
+ #
+ # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
+ # the discussion in the overview notes.
+ #
+ # If both of +user+ and +secret+ are given, SMTP authentication
+ # will be attempted using the AUTH command. +authtype+ specifies
+ # the type of authentication to attempt; it must be one of
+ # :login, :plain, and :cram_md5. See the notes on SMTP Authentication
+ # in the overview.
+ #
+ # === Block Usage
+ #
+ # When this methods is called with a block, the newly-started SMTP
+ # object is yielded to the block, and automatically closed after
+ # the block call finishes. Otherwise, it is the caller's
+ # responsibility to close the session when finished.
+ #
+ # === Example
+ #
+ # This is very similar to the class method SMTP.start.
+ #
+ # require 'net/smtp'
+ # smtp = Net::SMTP.new('smtp.mail.server', 25)
+ # smtp.start(helo_domain, account, password, authtype) do |smtp|
+ # smtp.send_message msgstr, 'from@example.com', ['dest@example.com']
+ # end
+ #
+ # The primary use of this method (as opposed to SMTP.start)
+ # is probably to set debugging (#set_debug_output) or ESMTP
+ # (#esmtp=), which must be done before the session is
+ # started.
+ #
+ # === Errors
+ #
+ # If session has already been started, an IOError will be raised.
+ #
+ # This method may raise:
+ #
+ # * Net::SMTPAuthenticationError
+ # * Net::SMTPServerBusy
+ # * Net::SMTPSyntaxError
+ # * Net::SMTPFatalError
+ # * Net::SMTPUnknownError
+ # * IOError
+ # * TimeoutError
+ #
+ def start( helo = 'localhost.localdomain',
+ user = nil, secret = nil, authtype = nil ) # :yield: smtp
+ if block_given?
+ begin
+ do_start(helo, user, secret, authtype)
+ return yield(self)
+ ensure
+ do_finish
+ end
+ else
+ do_start(helo, user, secret, authtype)
+ return self
+ end
+ end
+
+ def do_start( helodomain, user, secret, authtype )
+ raise IOError, 'SMTP session already started' if @started
+ check_auth_args user, secret, authtype if user or secret
+
+ @socket = InternetMessageIO.old_open(@address, @port,
+ @open_timeout, @read_timeout,
+ @debug_output)
+ check_response(critical { recv_response() })
+ begin
+ if @esmtp
+ ehlo helodomain
+ else
+ helo helodomain
+ end
+ rescue ProtocolError
+ if @esmtp
+ @esmtp = false
+ @error_occured = false
+ retry
+ end
+ raise
+ end
+ authenticate user, secret, authtype if user
+ @started = true
+ ensure
+ @socket.close if not @started and @socket and not @socket.closed?
+ end
+ private :do_start
+
+ # Finishes the SMTP session and closes TCP connection.
+ # Raises IOError if not started.
+ def finish
+ raise IOError, 'not yet started' unless started?
+ do_finish
+ end
+
+ def do_finish
+ quit if @socket and not @socket.closed? and not @error_occured
+ ensure
+ @started = false
+ @error_occured = false
+ @socket.close if @socket and not @socket.closed?
+ @socket = nil
+ end
+ private :do_finish
+
+ #
+ # message send
+ #
+
+ public
+
+ #
+ # Sends +msgstr+ as a message. Single CR ("\r") and LF ("\n") found
+ # in the +msgstr+, are converted into the CR LF pair. You cannot send a
+ # binary message with this method. +msgstr+ should include both
+ # the message headers and body.
+ #
+ # +from_addr+ is a String representing the source mail address.
+ #
+ # +to_addr+ is a String or Strings or Array of Strings, representing
+ # the destination mail address or addresses.
+ #
+ # === Example
+ #
+ # Net::SMTP.start('smtp.example.com') do |smtp|
+ # smtp.send_message msgstr,
+ # 'from@example.com',
+ # ['dest@example.com', 'dest2@example.com']
+ # end
+ #
+ # === Errors
+ #
+ # This method may raise:
+ #
+ # * Net::SMTPServerBusy
+ # * Net::SMTPSyntaxError
+ # * Net::SMTPFatalError
+ # * Net::SMTPUnknownError
+ # * IOError
+ # * TimeoutError
+ #
+ def send_message( msgstr, from_addr, *to_addrs )
+ send0(from_addr, to_addrs.flatten) {
+ @socket.write_message msgstr
+ }
+ end
+
+ alias send_mail send_message
+ alias sendmail send_message # obsolete
+
+ #
+ # Opens a message writer stream and gives it to the block.
+ # The stream is valid only in the block, and has these methods:
+ #
+ # puts(str = ''):: outputs STR and CR LF.
+ # print(str):: outputs STR.
+ # printf(fmt, *args):: outputs sprintf(fmt,*args).
+ # write(str):: outputs STR and returns the length of written bytes.
+ # <<(str):: outputs STR and returns self.
+ #
+ # If a single CR ("\r") or LF ("\n") is found in the message,
+ # it is converted to the CR LF pair. You cannot send a binary
+ # message with this method.
+ #
+ # === Parameters
+ #
+ # +from_addr+ is a String representing the source mail address.
+ #
+ # +to_addr+ is a String or Strings or Array of Strings, representing
+ # the destination mail address or addresses.
+ #
+ # === Example
+ #
+ # Net::SMTP.start('smtp.example.com', 25) do |smtp|
+ # smtp.open_message_stream('from@example.com', ['dest@example.com']) do |f|
+ # f.puts 'From: from@example.com'
+ # f.puts 'To: dest@example.com'
+ # f.puts 'Subject: test message'
+ # f.puts
+ # f.puts 'This is a test message.'
+ # end
+ # end
+ #
+ # === Errors
+ #
+ # This method may raise:
+ #
+ # * Net::SMTPServerBusy
+ # * Net::SMTPSyntaxError
+ # * Net::SMTPFatalError
+ # * Net::SMTPUnknownError
+ # * IOError
+ # * TimeoutError
+ #
+ def open_message_stream( from_addr, *to_addrs, &block ) # :yield: stream
+ send0(from_addr, to_addrs.flatten) {
+ @socket.write_message_by_block(&block)
+ }
+ end
+
+ alias ready open_message_stream # obsolete
+
+ private
+
+ def send0( from_addr, to_addrs )
+ raise IOError, 'closed session' unless @socket
+ raise ArgumentError, 'mail destination not given' if to_addrs.empty?
+ if $SAFE > 0
+ raise SecurityError, 'tainted from_addr' if from_addr.tainted?
+ to_addrs.each do |to|
+ raise SecurityError, 'tainted to_addr' if to.tainted?
+ end
+ end
+
+ mailfrom from_addr
+ to_addrs.each do |to|
+ rcptto to
+ end
+ res = critical {
+ check_response(get_response('DATA'), true)
+ yield
+ recv_response()
+ }
+ check_response(res)
+ end
+
+ #
+ # auth
+ #
+
+ private
+
+ def check_auth_args( user, secret, authtype )
+ raise ArgumentError, 'both user and secret are required'\
+ unless user and secret
+ auth_method = "auth_#{authtype || 'cram_md5'}"
+ raise ArgumentError, "wrong auth type #{authtype}"\
+ unless respond_to?(auth_method, true)
+ end
+
+ def authenticate( user, secret, authtype )
+ __send__("auth_#{authtype || 'cram_md5'}", user, secret)
+ end
+
+ def auth_plain( user, secret )
+ res = critical { get_response('AUTH PLAIN %s',
+ base64_encode("\0#{user}\0#{secret}")) }
+ raise SMTPAuthenticationError, res unless /\A2../ === res
+ end
+
+ def auth_login( user, secret )
+ res = critical {
+ check_response(get_response('AUTH LOGIN'), true)
+ check_response(get_response(base64_encode(user)), true)
+ get_response(base64_encode(secret))
+ }
+ raise SMTPAuthenticationError, res unless /\A2../ === res
+ end
+
+ def auth_cram_md5( user, secret )
+ # CRAM-MD5: [RFC2195]
+ res = nil
+ critical {
+ res = check_response(get_response('AUTH CRAM-MD5'), true)
+ challenge = res.split(/ /)[1].unpack('m')[0]
+ secret = Digest::MD5.digest(secret) if secret.size > 64
+
+ isecret = secret + "\0" * (64 - secret.size)
+ osecret = isecret.dup
+ 0.upto(63) do |i|
+ isecret[i] ^= 0x36
+ osecret[i] ^= 0x5c
+ end
+ tmp = Digest::MD5.digest(isecret + challenge)
+ tmp = Digest::MD5.hexdigest(osecret + tmp)
+
+ res = get_response(base64_encode(user + ' ' + tmp))
+ }
+ raise SMTPAuthenticationError, res unless /\A2../ === res
+ end
+
+ def base64_encode( str )
+ # expects "str" may not become too long
+ [str].pack('m').gsub(/\s+/, '')
+ end
+
+ #
+ # SMTP command dispatcher
+ #
+
+ private
+
+ def helo( domain )
+ getok('HELO %s', domain)
+ end
+
+ def ehlo( domain )
+ getok('EHLO %s', domain)
+ end
+
+ def mailfrom( fromaddr )
+ getok('MAIL FROM:<%s>', fromaddr)
+ end
+
+ def rcptto( to )
+ getok('RCPT TO:<%s>', to)
+ end
+
+ def quit
+ getok('QUIT')
+ end
+
+ #
+ # row level library
+ #
+
+ private
+
+ def getok( fmt, *args )
+ res = critical {
+ @socket.writeline sprintf(fmt, *args)
+ recv_response()
+ }
+ return check_response(res)
+ end
+
+ def get_response( fmt, *args )
+ @socket.writeline sprintf(fmt, *args)
+ recv_response()
+ end
+
+ def recv_response
+ res = ''
+ while true
+ line = @socket.readline
+ res << line << "\n"
+ break unless line[3] == ?- # "210-PIPELINING"
+ end
+ res
+ end
+
+ def check_response( res, allow_continue = false )
+ return res if /\A2/ === res
+ return res if allow_continue and /\A3/ === res
+ err = case res
+ when /\A4/ then SMTPServerBusy
+ when /\A50/ then SMTPSyntaxError
+ when /\A55/ then SMTPFatalError
+ else SMTPUnknownError
+ end
+ raise err, res
+ end
+
+ def critical( &block )
+ return '200 dummy reply code' if @error_occured
+ begin
+ return yield()
+ rescue Exception
+ @error_occured = true
+ raise
+ end
+ end
+
+ end # class SMTP
+
+ SMTPSession = SMTP
+
+end # module Net
diff --git a/ruby_1_8_6/lib/net/telnet.rb b/ruby_1_8_6/lib/net/telnet.rb
new file mode 100644
index 0000000000..c5f8b0429a
--- /dev/null
+++ b/ruby_1_8_6/lib/net/telnet.rb
@@ -0,0 +1,742 @@
+# = net/telnet.rb - Simple Telnet Client Library
+#
+# Author:: Wakou Aoyama <wakou@ruby-lang.org>
+# Documentation:: William Webber and Wakou Aoyama
+#
+# This file holds the class Net::Telnet, which provides client-side
+# telnet functionality.
+#
+# For documentation, see Net::Telnet.
+#
+
+require "socket"
+require "delegate"
+require "timeout"
+require "English"
+
+module Net
+
+ #
+ # == Net::Telnet
+ #
+ # Provides telnet client functionality.
+ #
+ # This class also has, through delegation, all the methods of a
+ # socket object (by default, a +TCPSocket+, but can be set by the
+ # +Proxy+ option to <tt>new()</tt>). This provides methods such as
+ # <tt>close()</tt> to end the session and <tt>sysread()</tt> to read
+ # data directly from the host, instead of via the <tt>waitfor()</tt>
+ # mechanism. Note that if you do use <tt>sysread()</tt> directly
+ # when in telnet mode, you should probably pass the output through
+ # <tt>preprocess()</tt> to extract telnet command sequences.
+ #
+ # == Overview
+ #
+ # The telnet protocol allows a client to login remotely to a user
+ # account on a server and execute commands via a shell. The equivalent
+ # is done by creating a Net::Telnet class with the +Host+ option
+ # set to your host, calling #login() with your user and password,
+ # issuing one or more #cmd() calls, and then calling #close()
+ # to end the session. The #waitfor(), #print(), #puts(), and
+ # #write() methods, which #cmd() is implemented on top of, are
+ # only needed if you are doing something more complicated.
+ #
+ # A Net::Telnet object can also be used to connect to non-telnet
+ # services, such as SMTP or HTTP. In this case, you normally
+ # want to provide the +Port+ option to specify the port to
+ # connect to, and set the +Telnetmode+ option to false to prevent
+ # the client from attempting to interpret telnet command sequences.
+ # Generally, #login() will not work with other protocols, and you
+ # have to handle authentication yourself.
+ #
+ # For some protocols, it will be possible to specify the +Prompt+
+ # option once when you create the Telnet object and use #cmd() calls;
+ # for others, you will have to specify the response sequence to
+ # look for as the Match option to every #cmd() call, or call
+ # #puts() and #waitfor() directly; for yet others, you will have
+ # to use #sysread() instead of #waitfor() and parse server
+ # responses yourself.
+ #
+ # It is worth noting that when you create a new Net::Telnet object,
+ # you can supply a proxy IO channel via the Proxy option. This
+ # can be used to attach the Telnet object to other Telnet objects,
+ # to already open sockets, or to any read-write IO object. This
+ # can be useful, for instance, for setting up a test fixture for
+ # unit testing.
+ #
+ # == Examples
+ #
+ # === Log in and send a command, echoing all output to stdout
+ #
+ # 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
+ #
+ #
+ # === Check 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 }
+ #
+ # == References
+ #
+ # There are a large number of RFCs relevant to the Telnet protocol.
+ # RFCs 854-861 define the base protocol. For a complete listing
+ # of relevant RFCs, see
+ # http://www.omnifarious.org/~hopper/technical/telnet-rfc.html
+ #
+ class Telnet < SimpleDelegator
+
+ # :stopdoc:
+ 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
+ REVISION = '$Id$'
+ # :startdoc:
+
+ #
+ # Creates a new Net::Telnet object.
+ #
+ # Attempts to connect to the host (unless the Proxy option is
+ # provided: see below). If a block is provided, it is yielded
+ # status messages on the attempt to connect to the server, of
+ # the form:
+ #
+ # Trying localhost...
+ # Connected to localhost.
+ #
+ # +options+ is a hash of options. The following example lists
+ # all options and their default values.
+ #
+ # host = Net::Telnet::new(
+ # "Host" => "localhost", # default: "localhost"
+ # "Port" => 23, # default: 23
+ # "Binmode" => false, # default: false
+ # "Output_log" => "output_log", # default: nil (no output)
+ # "Dump_log" => "dump_log", # default: nil (no output)
+ # "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 Net::Telnet or IO object
+ # )
+ #
+ # The options have the following meanings:
+ #
+ # Host:: the hostname or IP address of the host to connect to, as a String.
+ # Defaults to "localhost".
+ #
+ # Port:: the port to connect to. Defaults to 23.
+ #
+ # Binmode:: if false (the default), newline substitution is performed.
+ # Outgoing LF is
+ # converted to CRLF, and incoming CRLF is converted to LF. If
+ # true, this substitution is not performed. This value can
+ # also be set with the #binmode() method. The
+ # outgoing conversion only applies to the #puts() and #print()
+ # methods, not the #write() method. The precise nature of
+ # the newline conversion is also affected by the telnet options
+ # SGA and BIN.
+ #
+ # Output_log:: the name of the file to write connection status messages
+ # and all received traffic to. In the case of a proper
+ # Telnet session, this will include the client input as
+ # echoed by the host; otherwise, it only includes server
+ # responses. Output is appended verbatim to this file.
+ # By default, no output log is kept.
+ #
+ # Dump_log:: as for Output_log, except that output is written in hexdump
+ # format (16 bytes per line as hex pairs, followed by their
+ # printable equivalent), with connection status messages
+ # preceded by '#', sent traffic preceded by '>', and
+ # received traffic preceded by '<'. By default, not dump log
+ # is kept.
+ #
+ # Prompt:: a regular expression matching the host's command-line prompt
+ # sequence. This is needed by the Telnet class to determine
+ # when the output from a command has finished and the host is
+ # ready to receive a new command. By default, this regular
+ # expression is /[$%#>] \z/n.
+ #
+ # Telnetmode:: a boolean value, true by default. In telnet mode,
+ # traffic received from the host is parsed for special
+ # command sequences, and these sequences are escaped
+ # in outgoing traffic sent using #puts() or #print()
+ # (but not #write()). If you are using the Net::Telnet
+ # object to connect to a non-telnet service (such as
+ # SMTP or POP), this should be set to "false" to prevent
+ # undesired data corruption. This value can also be set
+ # by the #telnetmode() method.
+ #
+ # Timeout:: the number of seconds to wait before timing out both the
+ # initial attempt to connect to host (in this constructor),
+ # and all attempts to read data from the host (in #waitfor(),
+ # #cmd(), and #login()). Exceeding this timeout causes a
+ # TimeoutError to be raised. The default value is 10 seconds.
+ # You can disable the timeout by setting this value to false.
+ # In this case, the connect attempt will eventually timeout
+ # on the underlying connect(2) socket call with an
+ # Errno::ETIMEDOUT error (but generally only after a few
+ # minutes), but other attempts to read data from the host
+ # will hand indefinitely if no data is forthcoming.
+ #
+ # Waittime:: the amount of time to wait after seeing what looks like a
+ # prompt (that is, received data that matches the Prompt
+ # option regular expression) to see if more data arrives.
+ # If more data does arrive in this time, Net::Telnet assumes
+ # that what it saw was not really a prompt. This is to try to
+ # avoid false matches, but it can also lead to missing real
+ # prompts (if, for instance, a background process writes to
+ # the terminal soon after the prompt is displayed). By
+ # default, set to 0, meaning not to wait for more data.
+ #
+ # Proxy:: a proxy object to used instead of opening a direct connection
+ # to the host. Must be either another Net::Telnet object or
+ # an IO object. If it is another Net::Telnet object, this
+ # instance will use that one's socket for communication. If an
+ # IO object, it is used directly for communication. Any other
+ # kind of object will cause an error to be raised.
+ #
+ def initialize(options) # :yield: mesg
+ @options = options
+ @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["Timeout"] = 10 unless @options.has_key?("Timeout")
+ @options["Waittime"] = 0 unless @options.has_key?("Waittime")
+ unless @options.has_key?("Binmode")
+ @options["Binmode"] = false
+ else
+ unless (true == @options["Binmode"] or false == @options["Binmode"])
+ raise ArgumentError, "Binmode option must be true or false"
+ end
+ end
+
+ unless @options.has_key?("Telnetmode")
+ @options["Telnetmode"] = true
+ else
+ unless (true == @options["Telnetmode"] or false == @options["Telnetmode"])
+ raise ArgumentError, "Telnetmode option must be true or false"
+ end
+ end
+
+ @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) # :nodoc:
+ 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 += ' ' * (32 - hexvals.length)
+ hexvals = format("%s %s %s %s " * 4, *hexvals.unpack('a2' * 16))
+ line = 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?(Net::Telnet)
+ @sock = @options["Proxy"].sock
+ elsif @options["Proxy"].kind_of?(IO)
+ @sock = @options["Proxy"]
+ else
+ raise "Error: Proxy must be an instance of Net::Telnet or IO."
+ end
+ else
+ message = "Trying " + @options["Host"] + "...\n"
+ yield(message) if block_given?
+ @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"]) do
+ @sock = TCPSocket.open(@options["Host"], @options["Port"])
+ end
+ end
+ rescue TimeoutError
+ raise TimeoutError, "timed out while opening a connection to the host"
+ rescue
+ @log.write($ERROR_INFO.to_s + "\n") if @options.has_key?("Output_log")
+ @dumplog.log_dump('#', $ERROR_INFO.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 block_given?
+ @log.write(message) if @options.has_key?("Output_log")
+ @dumplog.log_dump('#', message) if @options.has_key?("Dump_log")
+ end
+
+ super(@sock)
+ end # initialize
+
+ # The socket the Telnet object is using. Note that this object becomes
+ # a delegate of the Telnet object, so normally you invoke its methods
+ # directly on the Telnet object.
+ attr :sock
+
+ # Set telnet command interpretation on (+mode+ == true) or off
+ # (+mode+ == false), or return the current value (+mode+ not
+ # provided). It should be on for true telnet sessions, off if
+ # using Net::Telnet to connect to a non-telnet service such
+ # as SMTP.
+ def telnetmode(mode = nil)
+ case mode
+ when nil
+ @options["Telnetmode"]
+ when true, false
+ @options["Telnetmode"] = mode
+ else
+ raise ArgumentError, "argument must be true or false, or missing"
+ end
+ end
+
+ # Turn telnet command interpretation on (true) or off (false). It
+ # should be on for true telnet sessions, off if using Net::Telnet
+ # to connect to a non-telnet service such as SMTP.
+ def telnetmode=(mode)
+ if (true == mode or false == mode)
+ @options["Telnetmode"] = mode
+ else
+ raise ArgumentError, "argument must be true or false"
+ end
+ end
+
+ # Turn newline conversion on (+mode+ == false) or off (+mode+ == true),
+ # or return the current value (+mode+ is not specified).
+ def binmode(mode = nil)
+ case mode
+ when nil
+ @options["Binmode"]
+ when true, false
+ @options["Binmode"] = mode
+ else
+ raise ArgumentError, "argument must be true or false"
+ end
+ end
+
+ # Turn newline conversion on (false) or off (true).
+ def binmode=(mode)
+ if (true == mode or false == mode)
+ @options["Binmode"] = mode
+ else
+ raise ArgumentError, "argument must be true or false"
+ end
+ end
+
+ # Preprocess received data from the host.
+ #
+ # Performs newline conversion and detects telnet command sequences.
+ # Called automatically by #waitfor(). You should only use this
+ # method yourself if you have read input directly using sysread()
+ # or similar, and even then only if in telnet mode.
+ def preprocess(string)
+ # combine CR+NULL into CR
+ string = string.gsub(/#{CR}#{NULL}/no, CR) if @options["Telnetmode"]
+
+ # combine EOL into "\n"
+ string = string.gsub(/#{EOL}/no, "\n") unless @options["Binmode"]
+
+ string.gsub(/#{IAC}(
+ [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|
+ [#{DO}#{DONT}#{WILL}#{WONT}]
+ [#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]|
+ #{SB}[^#{IAC}]*#{IAC}#{SE}
+ )/xno) do
+ 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
+ end
+ end # preprocess
+
+ # Read data from the host until a certain sequence is matched.
+ #
+ # If a block is given, the received data will be yielded as it
+ # is read in (not necessarily all in one go), or nil if EOF
+ # occurs before any data is received. Whether a block is given
+ # or not, all data read will be returned in a single string, or again
+ # nil if EOF occurs before any data is received. Note that
+ # received data includes the matched sequence we were looking for.
+ #
+ # +options+ can be either a regular expression or a hash of options.
+ # If a regular expression, this specifies the data to wait for.
+ # If a hash, this can specify the following options:
+ #
+ # Match:: a regular expression, specifying the data to wait for.
+ # Prompt:: as for Match; used only if Match is not specified.
+ # String:: as for Match, except a string that will be converted
+ # into a regular expression. Used only if Match and
+ # Prompt are not specified.
+ # Timeout:: the number of seconds to wait for data from the host
+ # before raising a TimeoutError. If set to false,
+ # no timeout will occur. If not specified, the
+ # Timeout option value specified when this instance
+ # was created will be used, or, failing that, the
+ # default value of 10 seconds.
+ # Waittime:: the number of seconds to wait after matching against
+ # the input data to see if more data arrives. If more
+ # data arrives within this time, we will judge ourselves
+ # not to have matched successfully, and will continue
+ # trying to match. If not specified, the Waittime option
+ # value specified when this instance was created will be
+ # used, or, failing that, the default value of 0 seconds,
+ # which means not to wait for more input.
+ #
+ def waitfor(options) # :yield: recvdata
+ 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 while waiting for more data"
+ end
+ begin
+ c = @sock.readpartial(1024 * 1024)
+ @dumplog.log_dump('<', c) if @options.has_key?("Dump_log")
+ if @options["Telnetmode"]
+ c = rest + c
+ if Integer(c.rindex(/#{IAC}#{SE}/no)) <
+ Integer(c.rindex(/#{IAC}#{SB}/no))
+ buf = preprocess(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(c[0 ... pt])
+ rest = c[pt .. -1]
+ else
+ buf = preprocess(c)
+ rest = ''
+ end
+ else
+ # Not Telnetmode.
+ #
+ # We cannot use preprocess() on this data, because that
+ # method makes some Telnetmode-specific assumptions.
+ buf = c
+ buf.gsub!(/#{EOL}/no, "\n") unless @options["Binmode"]
+ rest = ''
+ end
+ @log.print(buf) if @options.has_key?("Output_log")
+ line += buf
+ yield buf if block_given?
+ rescue EOFError # End of file reached
+ if line == ''
+ line = nil
+ yield nil if block_given?
+ end
+ break
+ end
+ end
+ line
+ end
+
+ # Write +string+ to the host.
+ #
+ # Does not perform any conversions on +string+. Will log +string+ to the
+ # dumplog, if the Dump_log option is set.
+ 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
+
+ # Sends a string to the host.
+ #
+ # This does _not_ automatically append a newline to the string. Embedded
+ # newlines may be converted and telnet command sequences escaped
+ # depending upon the values of telnetmode, binmode, and telnet options
+ # set by the host.
+ def print(string)
+ string = string.gsub(/#{IAC}/no, IAC + IAC) if @options["Telnetmode"]
+
+ if @options["Binmode"]
+ self.write(string)
+ else
+ if @telnet_option["BINARY"] and @telnet_option["SGA"]
+ # IAC WILL SGA IAC DO BIN send EOL --> CR
+ self.write(string.gsub(/\n/n, CR))
+ elsif @telnet_option["SGA"]
+ # IAC WILL SGA send EOL --> CR+NULL
+ self.write(string.gsub(/\n/n, CR + NULL))
+ else
+ # NONE send EOL --> CR+LF
+ self.write(string.gsub(/\n/n, EOL))
+ end
+ end
+ end
+
+ # Sends a string to the host.
+ #
+ # Same as #print(), but appends a newline to the string.
+ def puts(string)
+ self.print(string + "\n")
+ end
+
+ # Send a command to the host.
+ #
+ # More exactly, sends a string to the host, and reads in all received
+ # data until is sees the prompt or other matched sequence.
+ #
+ # If a block is given, the received data will be yielded to it as
+ # it is read in. Whether a block is given or not, the received data
+ # will be return as a string. Note that the received data includes
+ # the prompt and in most cases the host's echo of our command.
+ #
+ # +options+ is either a String, specified the string or command to
+ # send to the host; or it is a hash of options. If a hash, the
+ # following options can be specified:
+ #
+ # String:: the command or other string to send to the host.
+ # Match:: a regular expression, the sequence to look for in
+ # the received data before returning. If not specified,
+ # the Prompt option value specified when this instance
+ # was created will be used, or, failing that, the default
+ # prompt of /[$%#>] \z/n.
+ # Timeout:: the seconds to wait for data from the host before raising
+ # a Timeout error. If not specified, the Timeout option
+ # value specified when this instance was created will be
+ # used, or, failing that, the default value of 10 seconds.
+ #
+ # The command or other string will have the newline sequence appended
+ # to it.
+ def cmd(options) # :yield: recvdata
+ 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.puts(string)
+ if block_given?
+ waitfor({"Prompt" => match, "Timeout" => time_out}){|c| yield c }
+ else
+ waitfor({"Prompt" => match, "Timeout" => time_out})
+ end
+ end
+
+ # Login to the host with a given username and password.
+ #
+ # The username and password can either be provided as two string
+ # arguments in that order, or as a hash with keys "Name" and
+ # "Password".
+ #
+ # This method looks for the strings "login" and "Password" from the
+ # host to determine when to send the username and password. If the
+ # login sequence does not follow this pattern (for instance, you
+ # are connecting to a service other than telnet), you will need
+ # to handle login yourself.
+ #
+ # The password can be omitted, either by only
+ # provided one String argument, which will be used as the username,
+ # or by providing a has that has no "Password" key. In this case,
+ # the method will not look for the "Password:" prompt; if it is
+ # sent, it will have to be dealt with by later calls.
+ #
+ # The method returns all data received during the login process from
+ # the host, including the echoed username but not the password (which
+ # the host should not echo). If a block is passed in, this received
+ # data is also yielded to the block as it is received.
+ def login(options, password = nil) # :yield: recvdata
+ login_prompt = /[Ll]ogin[: ]*\z/n
+ password_prompt = /Password[: ]*\z/n
+ if options.kind_of?(Hash)
+ username = options["Name"]
+ password = options["Password"]
+ login_prompt = options["LoginPrompt"] if options["LoginPrompt"]
+ password_prompt = options["PasswordPrompt"] if options["PasswordPrompt"]
+ else
+ username = options
+ end
+
+ if block_given?
+ line = waitfor(login_prompt){|c| yield c }
+ if password
+ line += cmd({"String" => username,
+ "Match" => password_prompt}){|c| yield c }
+ line += cmd(password){|c| yield c }
+ else
+ line += cmd(username){|c| yield c }
+ end
+ else
+ line = waitfor(login_prompt)
+ if password
+ line += cmd({"String" => username,
+ "Match" => password_prompt})
+ line += cmd(password)
+ else
+ line += cmd(username)
+ end
+ end
+ line
+ end
+
+ end # class Telnet
+end # module Net
+