From 9ff1e787f915539b1980654e3d3d2013ff5c81d2 Mon Sep 17 00:00:00 2001 From: shyouhei Date: Mon, 7 Jul 2008 07:38:25 +0000 Subject: wrong commit; sorry git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/tags/v1_8_6_269@17938 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ruby_1_8_6/lib/net/ftp.rb | 923 ----------- ruby_1_8_6/lib/net/http.rb | 2277 --------------------------- ruby_1_8_6/lib/net/https.rb | 173 --- ruby_1_8_6/lib/net/imap.rb | 3371 ---------------------------------------- ruby_1_8_6/lib/net/pop.rb | 881 ----------- ruby_1_8_6/lib/net/protocol.rb | 390 ----- ruby_1_8_6/lib/net/smtp.rb | 697 --------- ruby_1_8_6/lib/net/telnet.rb | 749 --------- 8 files changed, 9461 deletions(-) delete mode 100644 ruby_1_8_6/lib/net/ftp.rb delete mode 100644 ruby_1_8_6/lib/net/http.rb delete mode 100644 ruby_1_8_6/lib/net/https.rb delete mode 100644 ruby_1_8_6/lib/net/imap.rb delete mode 100644 ruby_1_8_6/lib/net/pop.rb delete mode 100644 ruby_1_8_6/lib/net/protocol.rb delete mode 100644 ruby_1_8_6/lib/net/smtp.rb delete mode 100644 ruby_1_8_6/lib/net/telnet.rb (limited to 'ruby_1_8_6/lib/net') diff --git a/ruby_1_8_6/lib/net/ftp.rb b/ruby_1_8_6/lib/net/ftp.rb deleted file mode 100644 index dfbcf1499f..0000000000 --- a/ruby_1_8_6/lib/net/ftp.rb +++ /dev/null @@ -1,923 +0,0 @@ -# -# = net/ftp.rb - FTP Client Library -# -# Written by Shugo Maeda . -# -# 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 FTP.new, 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 - # Errno::ECONNREFUSED) 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 user@host 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 - # Net::FTPPermError). - # - 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 deleted file mode 100644 index 7dd1f24d4c..0000000000 --- a/ruby_1_8_6/lib/net/http.rb +++ /dev/null @@ -1,2277 +0,0 @@ -# -# = 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 . -# HTTPS support added by GOTOU Yuuzou . -# -# 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 - if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE - s.post_connection_check(@address) - end - 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 = %ri.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 deleted file mode 100644 index e296dbbed4..0000000000 --- a/ruby_1_8_6/lib/net/https.rb +++ /dev/null @@ -1,173 +0,0 @@ -=begin - -= $RCSfile$ -- SSL/TLS enhancement for Net::HTTP. - -== Info - 'OpenSSL for Ruby 2' project - Copyright (C) 2001 GOTOU Yuuzou - 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 deleted file mode 100644 index f8c0d3be4c..0000000000 --- a/ruby_1_8_6/lib/net/imap.rb +++ /dev/null @@ -1,3371 +0,0 @@ -# -# = net/imap.rb -# -# Copyright (C) 2000 Shugo Maeda -# -# 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/%") - # #=> [#, \\ - # #, \\ - # #] - 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", <:: 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 :: messages with an internal date strictly before - # . The date argument has a format similar - # to 8-Aug-2002. - # - # BODY :: messages that contain within their body. - # - # CC :: messages containing in their CC field. - # - # FROM :: messages that contain in their FROM field. - # - # NEW:: messages with the \Recent, but not the \Seen, flag set. - # - # NOT :: negate the following search key. - # - # OR :: "or" two search keys together. - # - # ON :: messages with an internal date exactly equal to , - # which has a format similar to 8-Aug-2002. - # - # SINCE :: messages with an internal date on or after . - # - # SUBJECT :: messages with in their subject. - # - # TO :: messages with 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") - # #=> [#98}>, \\ - # #99}>, \\ - # #100}>] - # p imap.fetch(6, "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]) - # #=> [#[:Seen, :Deleted]}>, \\ - # #[:Seen, :Deleted]}>, \\ - # #[: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. - @sock.post_connection_check(@host) if verify - 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 (()) 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* - # - # 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 (()). - # - # 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 (()). - # - # 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*] - # - # ==== 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[
]<>] - # 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 < - - --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 <. -# -# 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 - @n_mails = nil - @n_bytes = 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 deleted file mode 100644 index d722fdcbd4..0000000000 --- a/ruby_1_8_6/lib/net/protocol.rb +++ /dev/null @@ -1,390 +0,0 @@ -# -# = net/protocol.rb -# -#-- -# Copyright (c) 1999-2005 Yukihiro Matsumoto -# Copyright (c) 1999-2005 Minero Aoki -# -# written and maintained by 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. -# -# $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 deleted file mode 100644 index 35108966c5..0000000000 --- a/ruby_1_8_6/lib/net/smtp.rb +++ /dev/null @@ -1,697 +0,0 @@ -# = net/smtp.rb -# -# Copyright (c) 1999-2003 Yukihiro Matsumoto. -# -# Copyright (c) 1999-2003 Minero Aoki. -# -# Written & maintained by Minero Aoki . -# -# 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 = < - # To: Destination Address - # Subject: test message - # Date: Sat, 23 Jun 2001 16:26:43 +0900 - # Message-Id: - # - # 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'. - # - # 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', - 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', - 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 deleted file mode 100644 index a55527f15e..0000000000 --- a/ruby_1_8_6/lib/net/telnet.rb +++ /dev/null @@ -1,749 +0,0 @@ -# = net/telnet.rb - Simple Telnet Client Library -# -# Author:: Wakou Aoyama -# 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 new()). This provides methods such as - # close() to end the session and sysread() to read - # data directly from the host, instead of via the waitfor() - # mechanism. Note that if you do use sysread() directly - # when in telnet mode, you should probably pass the output through - # preprocess() 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) || - c.rindex(/\r\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 = rest + c - rest = '' - unless @options["Binmode"] - if pt = buf.rindex(/\r\z/no) - buf = buf[0 ... pt] - rest = buf[pt .. -1] - end - buf.gsub!(/#{EOL}/no, "\n") - end - 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 = /[Pp]ass(?:word|phrase)[: ]*\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 - -- cgit v1.2.3