mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			1023 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			1023 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # frozen_string_literal: true
 | |
| # = net/pop.rb
 | |
| #
 | |
| # Copyright (c) 1999-2007 Yukihiro Matsumoto.
 | |
| #
 | |
| # Copyright (c) 1999-2007 Minero Aoki.
 | |
| #
 | |
| # Written & maintained by Minero Aoki <aamine@loveruby.net>.
 | |
| #
 | |
| # Documented by William Webber and Minero Aoki.
 | |
| #
 | |
| # This program is free software. You can re-distribute and/or
 | |
| # modify this program under the same terms as Ruby itself,
 | |
| # Ruby Distribute License.
 | |
| #
 | |
| # NOTE: You can find Japanese version of this document at:
 | |
| # http://docs.ruby-lang.org/ja/latest/library/net=2fpop.html
 | |
| #
 | |
| #   $Id$
 | |
| #
 | |
| # See Net::POP3 for documentation.
 | |
| #
 | |
| 
 | |
| require 'net/protocol'
 | |
| require 'digest/md5'
 | |
| require 'timeout'
 | |
| 
 | |
| begin
 | |
|   require "openssl"
 | |
| rescue LoadError
 | |
| end
 | |
| 
 | |
| 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
 | |
| 
 | |
|   #
 | |
|   # == 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($isapop).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
 | |
| 
 | |
|     # svn revision of this library
 | |
|     Revision = %q$Revision$.split[1]
 | |
| 
 | |
|     #
 | |
|     # Class Parameters
 | |
|     #
 | |
| 
 | |
|     # returns the port for POP3
 | |
|     def POP3.default_port
 | |
|       default_pop3_port()
 | |
|     end
 | |
| 
 | |
|     # The default port for POP3 connections, port 110
 | |
|     def POP3.default_pop3_port
 | |
|       110
 | |
|     end
 | |
| 
 | |
|     # The default port for POP3S connections, port 995
 | |
|     def POP3.default_pop3s_port
 | |
|       995
 | |
|     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
 | |
| 
 | |
|     #
 | |
|     # SSL
 | |
|     #
 | |
| 
 | |
|     @ssl_params = nil
 | |
| 
 | |
|     # :call-seq:
 | |
|     #    Net::POP.enable_ssl(params = {})
 | |
|     #
 | |
|     # Enable SSL for all new instances.
 | |
|     # +params+ is passed to OpenSSL::SSLContext#set_params.
 | |
|     def POP3.enable_ssl(*args)
 | |
|       @ssl_params = create_ssl_params(*args)
 | |
|     end
 | |
| 
 | |
|     # Constructs proper parameters from arguments
 | |
|     def POP3.create_ssl_params(verify_or_params = {}, certs = nil)
 | |
|       begin
 | |
|         params = verify_or_params.to_hash
 | |
|       rescue NoMethodError
 | |
|         params = {}
 | |
|         params[:verify_mode] = verify_or_params
 | |
|         if certs
 | |
|           if File.file?(certs)
 | |
|             params[:ca_file] = certs
 | |
|           elsif File.directory?(certs)
 | |
|             params[:ca_path] = certs
 | |
|           end
 | |
|         end
 | |
|       end
 | |
|       return params
 | |
|     end
 | |
| 
 | |
|     # Disable SSL for all new instances.
 | |
|     def POP3.disable_ssl
 | |
|       @ssl_params = nil
 | |
|     end
 | |
| 
 | |
|     # returns the SSL Parameters
 | |
|     #
 | |
|     # see also POP3.enable_ssl
 | |
|     def POP3.ssl_params
 | |
|       return @ssl_params
 | |
|     end
 | |
| 
 | |
|     # returns +true+ if POP3.ssl_params is set
 | |
|     def POP3.use_ssl?
 | |
|       return !@ssl_params.nil?
 | |
|     end
 | |
| 
 | |
|     # returns whether verify_mode is enable from POP3.ssl_params
 | |
|     def POP3.verify
 | |
|       return @ssl_params[:verify_mode]
 | |
|     end
 | |
| 
 | |
|     # returns the :ca_file or :ca_path from POP3.ssl_params
 | |
|     def POP3.certs
 | |
|       return @ssl_params[:ca_file] || @ssl_params[:ca_path]
 | |
|     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.
 | |
|     #
 | |
|     # 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
 | |
|       @ssl_params = POP3.ssl_params
 | |
|       @port = 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
 | |
| 
 | |
|     # does this instance use SSL?
 | |
|     def use_ssl?
 | |
|       return !@ssl_params.nil?
 | |
|     end
 | |
| 
 | |
|     # :call-seq:
 | |
|     #    Net::POP#enable_ssl(params = {})
 | |
|     #
 | |
|     # Enables SSL for this instance.  Must be called before the connection is
 | |
|     # established to have any effect.
 | |
|     # +params[:port]+ is port to establish the SSL connection on; Defaults to 995.
 | |
|     # +params+ (except :port) is passed to OpenSSL::SSLContext#set_params.
 | |
|     def enable_ssl(verify_or_params = {}, certs = nil, port = nil)
 | |
|       begin
 | |
|         @ssl_params = verify_or_params.to_hash.dup
 | |
|         @port = @ssl_params.delete(:port) || @port
 | |
|       rescue NoMethodError
 | |
|         @ssl_params = POP3.create_ssl_params(verify_or_params, certs)
 | |
|         @port = port || @port
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Disable SSL for all new instances.
 | |
|     def disable_ssl
 | |
|       @ssl_params = nil
 | |
|     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.
 | |
|     def port
 | |
|       return @port || (use_ssl? ? POP3.default_pop3s_port : POP3.default_pop3_port)
 | |
|     end
 | |
| 
 | |
|     # Seconds to wait until a connection is opened.
 | |
|     # If the POP3 object cannot open a connection within this time,
 | |
|     # it raises a Net::OpenTimeout exception. The default value is 30 seconds.
 | |
|     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 Net::ReadTimeout exception. The default value is 60 seconds.
 | |
|     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
 | |
| 
 | |
|     # internal method for Net::POP3.start
 | |
|     def do_start(account, password) # :nodoc:
 | |
|       s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do
 | |
|         TCPSocket.open(@address, port)
 | |
|       end
 | |
|       if use_ssl?
 | |
|         raise 'openssl library not installed' unless defined?(OpenSSL)
 | |
|         context = OpenSSL::SSL::SSLContext.new
 | |
|         context.set_params(@ssl_params)
 | |
|         s = OpenSSL::SSL::SSLSocket.new(s, context)
 | |
|         s.hostname = @address
 | |
|         s.sync_close = true
 | |
|         ssl_socket_connect(s, @open_timeout)
 | |
|         if context.verify_mode != OpenSSL::SSL::VERIFY_NONE
 | |
|           s.post_connection_check(@address)
 | |
|         end
 | |
|       end
 | |
|       @socket = InternetMessageIO.new(s,
 | |
|                                       read_timeout: @read_timeout,
 | |
|                                       debug_output: @debug_output)
 | |
|       logging "POP session started: #{@address}:#{@port} (#{@apop ? 'APOP' : 'POP'})"
 | |
|       on_connect
 | |
|       @command = POP3Command.new(@socket)
 | |
|       if apop?
 | |
|         @command.apop account, password
 | |
|       else
 | |
|         @command.auth account, password
 | |
|       end
 | |
|       @started = true
 | |
|     ensure
 | |
|       # Authentication failed, clean up connection.
 | |
|       unless @started
 | |
|         s.close if s
 | |
|         @socket = nil
 | |
|         @command = nil
 | |
|       end
 | |
|     end
 | |
|     private :do_start
 | |
| 
 | |
|     # Does nothing
 | |
|     def on_connect # :nodoc:
 | |
|     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
 | |
| 
 | |
|     # nil's out the:
 | |
|     # - mails
 | |
|     # - number counter for mails
 | |
|     # - number counter for bytes
 | |
|     # - quits the current command, if any
 | |
|     def do_finish # :nodoc:
 | |
|       @mails = nil
 | |
|       @n_mails = nil
 | |
|       @n_bytes = nil
 | |
|       @command.quit if @command
 | |
|     ensure
 | |
|       @started = false
 | |
|       @command = nil
 | |
|       @socket.close if @socket
 | |
|       @socket = nil
 | |
|     end
 | |
|     private :do_finish
 | |
| 
 | |
|     # Returns the current command.
 | |
|     #
 | |
|     # Raises IOError if there is no active socket
 | |
|     def command # :nodoc:
 | |
|       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)
 | |
|       uidl = command().uidl
 | |
|       @mails.each {|m| m.uid = uidl[m.number] }
 | |
|     end
 | |
| 
 | |
|     # debugging output for +msg+
 | |
|     def logging(msg)
 | |
|       @debug_output << msg + "\n" if @debug_output
 | |
|     end
 | |
| 
 | |
|   end   # class POP3
 | |
| 
 | |
|   # class aliases
 | |
|   POP = POP3 # :nodoc:
 | |
|   POPSession  = POP3 # :nodoc:
 | |
|   POP3Session = POP3 # :nodoc:
 | |
| 
 | |
|   #
 | |
|   # 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
 | |
|       @uid = uid
 | |
|     end
 | |
| 
 | |
|   end   # class POPMail
 | |
| 
 | |
| 
 | |
|   class POP3Command   #:nodoc: internal use only
 | |
| 
 | |
|     def initialize(sock)
 | |
|       @socket = sock
 | |
|       @error_occurred = false
 | |
|       res = check_response(critical { recv_response() })
 | |
|       @apop_stamp = res.slice(/<[!-~]+@[!-~]+>/)
 | |
|     end
 | |
| 
 | |
|     attr_reader :socket
 | |
| 
 | |
|     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_occurred
 | |
|       begin
 | |
|         return yield()
 | |
|       rescue Exception
 | |
|         @error_occurred = true
 | |
|         raise
 | |
|       end
 | |
|     end
 | |
| 
 | |
|   end   # class POP3Command
 | |
| 
 | |
| end   # module Net
 | 
