File: net/pop.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: Net#32
has properties
constant: POP #697
constant: POPSession #698
constant: POP3Session #699
constant: APOPSession #712
  class: POPError#36
inherits from
  ProtocolError ( Net )
  class: POPAuthenticationError#39
inherits from
  ProtoAuthError ( Net )
  class: POPBadResponse#42
inherits from
  POPError ( Net )
  class: POP3#197
inherits from
  Protocol ( Net )
has properties
constant: Revision #199
class method: default_port #205
class method: default_pop3_port #210
class method: default_pop3s_port #215
class method: socket_type #219
class method: APOP / 1 #238
class method: foreach / 6 #262
class method: delete_all (1/2) / 6 #283
class method: auth_only (1/2) / 5 #305
method: auth_only (2/E) / 2 #314
class method: enable_ssl (1/2) / 1 #332
class method: create_ssl_params / 2 #336
class method: disable_ssl (1/2) #354
class method: ssl_params #358
class method: use_ssl? (1/2) #362
class method: verify #366
class method: certs #370
class method: start (1/2) / 6 #394
method: initialize / 3 #410
method: apop? #429
method: use_ssl? (2/E) #434
method: enable_ssl (2/E) / 3 #445
method: disable_ssl (2/E) #455
method: inspect #460
method: set_debug_output / 1 #477
attribute: address [R] #482
method: port #485
attribute: open_timeout [RW] #492
attribute: read_timeout [R] #497
method: read_timeout= / 1 #500
method: started? #506
alias: active? started? #510
method: start (2/E) / 2 #518
method: do_start / 2 #533
method: on_connect #568
method: finish #573
method: do_finish #578
method: command #591
method: n_mails #603
method: n_bytes #610
method: mails #622
method: each_mail / 1 #644
alias: each each_mail #648
method: delete_all (2/E) #666
method: reset #676
method: set_all_uids / 1 #685
method: logging / 1 #690
  class: APOP#704
inherits from
  POP3 ( Net )
has properties
method: apop? #706
  class: POPMail#719
inherits from
  Object ( Builtin-Module )
has properties
method: initialize / 4 #721
attribute: number [R] #731
attribute: length [R] #734
alias: size length #735
method: inspect #738
method: pop / 2 #780
alias: all pop #792
alias: mail pop #793
method: top / 2 #800
method: header / 1 #812
method: delete #836
alias: delete! delete #841
method: deleted? #844
method: unique_id #852
alias: uidl unique_id #858
method: uid= / 1 #860
  class: POP3Command#867
inherits from
  Object ( Builtin-Module )
has properties
method: initialize / 1 #869
method: inspect #876
method: auth / 2 #880
method: apop / 2 #887
method: list #897
method: stat #910
method: rset #917
method: top / 3 #921
method: retr / 2 #928
method: dele / 1 #935
method: uidl / 1 #939
method: quit #956
method: getok / 2 #962
method: get_response / 2 #967
method: recv_response #972
method: check_response / 1 #976
method: check_response_auth / 1 #981
method: critical #986

Class Hierarchy

Object ( Builtin-Module )
Exception ( Builtin-Module )
StandardError ( Builtin-Module )
ProtocolError ( Net )
ProtoAuthError ( Net )
  POPAuthenticationError    #39
POPError ( Net ) — #36
  POPBadResponse    #42
Protocol ( Net )
POP3 ( Net ) — #197
  APOP    #704
POPMail ( Net ) — #719
POP3Command ( Net ) — #867

Code

   1  # = net/pop.rb
   2  #
   3  # Copyright (c) 1999-2007 Yukihiro Matsumoto.
   4  #
   5  # Copyright (c) 1999-2007 Minero Aoki.
   6  # 
   7  # Written & maintained by Minero Aoki <aamine@loveruby.net>.
   8  #
   9  # Documented by William Webber and Minero Aoki.
  10  # 
  11  # This program is free software. You can re-distribute and/or
  12  # modify this program under the same terms as Ruby itself,
  13  # Ruby Distribute License.
  14  # 
  15  # NOTE: You can find Japanese version of this document at:
  16  # http://www.ruby-lang.org/ja/man/html/net_pop.html
  17  # 
  18  #   $Id: pop.rb 29903 2010-11-24 07:38:32Z shyouhei $
  19  # 
  20  # See Net::POP3 for documentation.
  21  #
  22 
  23  require 'net/protocol'
  24  require 'digest/md5'
  25  require 'timeout'
  26 
  27  begin
  28    require "openssl/ssl"
  29  rescue LoadError
  30  end
  31 
  32  module Net
  33 
  34    # Non-authentication POP3 protocol error
  35    # (reply code "-ERR", except authentication).
  36    class POPError < ProtocolError; end
  37 
  38    # POP3 authentication error.
  39    class POPAuthenticationError < ProtoAuthError; end
  40 
  41    # Unexpected response from the server.
  42    class POPBadResponse < POPError; end
  43 
  44    #
  45    # = Net::POP3
  46    #
  47    # == What is This Library?
  48    # 
  49    # This library provides functionality for retrieving 
  50    # email via POP3, the Post Office Protocol version 3. For details
  51    # of POP3, see [RFC1939] (http://www.ietf.org/rfc/rfc1939.txt).
  52    # 
  53    # == Examples
  54    # 
  55    # === Retrieving Messages 
  56    # 
  57    # This example retrieves messages from the server and deletes them 
  58    # on the server.
  59    #
  60    # Messages are written to files named 'inbox/1', 'inbox/2', ....
  61    # Replace 'pop.example.com' with your POP3 server address, and
  62    # 'YourAccount' and 'YourPassword' with the appropriate account
  63    # details.
  64    # 
  65    #     require 'net/pop'
  66    # 
  67    #     pop = Net::POP3.new('pop.example.com')
  68    #     pop.start('YourAccount', 'YourPassword')             # (1)
  69    #     if pop.mails.empty?
  70    #       puts 'No mail.'
  71    #     else
  72    #       i = 0
  73    #       pop.each_mail do |m|   # or "pop.mails.each ..."   # (2)
  74    #         File.open("inbox/#{i}", 'w') do |f|
  75    #           f.write m.pop
  76    #         end
  77    #         m.delete
  78    #         i += 1
  79    #       end
  80    #       puts "#{pop.mails.size} mails popped."
  81    #     end
  82    #     pop.finish                                           # (3)
  83    # 
  84    # 1. Call Net::POP3#start and start POP session.
  85    # 2. Access messages by using POP3#each_mail and/or POP3#mails.
  86    # 3. Close POP session by calling POP3#finish or use the block form of #start.
  87    # 
  88    # === Shortened Code
  89    # 
  90    # The example above is very verbose. You can shorten the code by using
  91    # some utility methods. First, the block form of Net::POP3.start can
  92    # be used instead of POP3.new, POP3#start and POP3#finish.
  93    # 
  94    #     require 'net/pop'
  95    # 
  96    #     Net::POP3.start('pop.example.com', 110,
  97    #                     'YourAccount', 'YourPassword') do |pop|
  98    #       if pop.mails.empty?
  99    #         puts 'No mail.'
 100    #       else
 101    #         i = 0
 102    #         pop.each_mail do |m|   # or "pop.mails.each ..."
 103    #           File.open("inbox/#{i}", 'w') do |f|
 104    #             f.write m.pop
 105    #           end
 106    #           m.delete
 107    #           i += 1
 108    #         end
 109    #         puts "#{pop.mails.size} mails popped."
 110    #       end
 111    #     end
 112    # 
 113    # POP3#delete_all is an alternative for #each_mail and #delete.
 114    # 
 115    #     require 'net/pop'
 116    # 
 117    #     Net::POP3.start('pop.example.com', 110,
 118    #                     'YourAccount', 'YourPassword') do |pop|
 119    #       if pop.mails.empty?
 120    #         puts 'No mail.'
 121    #       else
 122    #         i = 1
 123    #         pop.delete_all do |m|
 124    #           File.open("inbox/#{i}", 'w') do |f|
 125    #             f.write m.pop
 126    #           end
 127    #           i += 1
 128    #         end
 129    #       end
 130    #     end
 131    # 
 132    # And here is an even shorter example.
 133    # 
 134    #     require 'net/pop'
 135    # 
 136    #     i = 0
 137    #     Net::POP3.delete_all('pop.example.com', 110,
 138    #                          'YourAccount', 'YourPassword') do |m|
 139    #       File.open("inbox/#{i}", 'w') do |f|
 140    #         f.write m.pop
 141    #       end
 142    #       i += 1
 143    #     end
 144    # 
 145    # === Memory Space Issues
 146    # 
 147    # All the examples above get each message as one big string.
 148    # This example avoids this.
 149    # 
 150    #     require 'net/pop'
 151    # 
 152    #     i = 1
 153    #     Net::POP3.delete_all('pop.example.com', 110,
 154    #                          'YourAccount', 'YourPassword') do |m|
 155    #       File.open("inbox/#{i}", 'w') do |f|
 156    #         m.pop do |chunk|    # get a message little by little.
 157    #           f.write chunk
 158    #         end
 159    #         i += 1
 160    #       end
 161    #     end
 162    # 
 163    # === Using APOP
 164    # 
 165    # The net/pop library supports APOP authentication.
 166    # To use APOP, use the Net::APOP class instead of the Net::POP3 class.
 167    # You can use the utility method, Net::POP3.APOP(). For example:
 168    # 
 169    #     require 'net/pop'
 170    # 
 171    #     # Use APOP authentication if $isapop == true
 172    #     pop = Net::POP3.APOP($is_apop).new('apop.example.com', 110)
 173    #     pop.start(YourAccount', 'YourPassword') do |pop|
 174    #       # Rest of the code is the same.
 175    #     end
 176    # 
 177    # === Fetch Only Selected Mail Using 'UIDL' POP Command
 178    # 
 179    # If your POP server provides UIDL functionality,
 180    # you can grab only selected mails from the POP server.
 181    # e.g.
 182    # 
 183    #     def need_pop?( id )
 184    #       # determine if we need pop this mail...
 185    #     end
 186    # 
 187    #     Net::POP3.start('pop.example.com', 110,
 188    #                     'Your account', 'Your password') do |pop|
 189    #       pop.mails.select { |m| need_pop?(m.unique_id) }.each do |m|
 190    #         do_something(m.pop)
 191    #       end
 192    #     end
 193    # 
 194    # The POPMail#unique_id() method returns the unique-id of the message as a
 195    # String. Normally the unique-id is a hash of the message.
 196    # 
 197    class POP3 < Protocol
 198 
 199      Revision = %q$Revision: 29903 $.split[1]
 200 
 201      #
 202      # Class Parameters
 203      #
 204 
 205      def POP3.default_port
 206        default_pop3_port()
 207      end
 208 
 209      # The default port for POP3 connections, port 110
 210      def POP3.default_pop3_port
 211        110
 212      end
 213      
 214      # The default port for POP3S connections, port 995
 215      def POP3.default_pop3s_port
 216        995
 217      end
 218 
 219      def POP3.socket_type   #:nodoc: obsolete
 220        Net::InternetMessageIO
 221      end
 222 
 223      #
 224      # Utilities
 225      #
 226 
 227      # Returns the APOP class if +isapop+ is true; otherwise, returns
 228      # the POP class.  For example:
 229      #
 230      #     # Example 1
 231      #     pop = Net::POP3::APOP($is_apop).new(addr, port)
 232      #
 233      #     # Example 2
 234      #     Net::POP3::APOP($is_apop).start(addr, port) do |pop|
 235      #       ....
 236      #     end
 237      #
 238      def POP3.APOP(isapop)
 239        isapop ? APOP : POP3
 240      end
 241 
 242      # Starts a POP3 session and iterates over each POPMail object,
 243      # yielding it to the +block+.
 244      # This method is equivalent to:
 245      #
 246      #     Net::POP3.start(address, port, account, password) do |pop|
 247      #       pop.each_mail do |m|
 248      #         yield m
 249      #       end
 250      #     end
 251      #
 252      # This method raises a POPAuthenticationError if authentication fails.
 253      #
 254      # === Example
 255      #
 256      #     Net::POP3.foreach('pop.example.com', 110,
 257      #                       'YourAccount', 'YourPassword') do |m|
 258      #       file.write m.pop
 259      #       m.delete if $DELETE
 260      #     end
 261      #
 262      def POP3.foreach(address, port = nil,
 263                       account = nil, password = nil,
 264                       isapop = false, &block)  # :yields: message
 265        start(address, port, account, password, isapop) {|pop|
 266          pop.each_mail(&block)
 267        }
 268      end
 269 
 270      # Starts a POP3 session and deletes all messages on the server.
 271      # If a block is given, each POPMail object is yielded to it before
 272      # being deleted.
 273      #
 274      # This method raises a POPAuthenticationError if authentication fails.
 275      #
 276      # === Example
 277      #
 278      #     Net::POP3.delete_all('pop.example.com', 110,
 279      #                          'YourAccount', 'YourPassword') do |m|
 280      #       file.write m.pop
 281      #     end
 282      #
 283      def POP3.delete_all(address, port = nil,
 284                          account = nil, password = nil,
 285                          isapop = false, &block)
 286        start(address, port, account, password, isapop) {|pop|
 287          pop.delete_all(&block)
 288        }
 289      end
 290 
 291      # Opens a POP3 session, attempts authentication, and quits.
 292      #
 293      # This method raises POPAuthenticationError if authentication fails.
 294      #
 295      # === Example: normal POP3
 296      #
 297      #     Net::POP3.auth_only('pop.example.com', 110,
 298      #                         'YourAccount', 'YourPassword')
 299      #
 300      # === Example: APOP
 301      #
 302      #     Net::POP3.auth_only('pop.example.com', 110,
 303      #                         'YourAccount', 'YourPassword', true)
 304      #
 305      def POP3.auth_only(address, port = nil,
 306                         account = nil, password = nil,
 307                         isapop = false)
 308        new(address, port, isapop).auth_only account, password
 309      end
 310 
 311      # Starts a pop3 session, attempts authentication, and quits.
 312      # This method must not be called while POP3 session is opened.
 313      # This method raises POPAuthenticationError if authentication fails.
 314      def auth_only(account, password)
 315        raise IOError, 'opening previously opened POP session' if started?
 316        start(account, password) {
 317          ;
 318        }
 319      end
 320 
 321      #
 322      # SSL
 323      #
 324 
 325      @ssl_params = nil
 326 
 327      # call-seq:
 328      #    Net::POP.enable_ssl(params = {})
 329      #
 330      # Enable SSL for all new instances.
 331      # +params+ is passed to OpenSSL::SSLContext#set_params.
 332      def POP3.enable_ssl(*args)
 333        @ssl_params = create_ssl_params(*args)
 334      end
 335 
 336      def POP3.create_ssl_params(verify_or_params = {}, certs = nil)
 337        begin
 338          params = verify_or_params.to_hash
 339        rescue NoMethodError
 340          params = {}
 341          params[:verify_mode] = verify_or_params
 342          if certs
 343            if File.file?(certs)
 344              params[:ca_file] = certs
 345            elsif File.directory?(certs)
 346              params[:ca_path] = certs
 347            end
 348          end
 349        end
 350        return params
 351      end
 352 
 353      # Disable SSL for all new instances.
 354      def POP3.disable_ssl
 355        @ssl_params = nil
 356      end
 357 
 358      def POP3.ssl_params
 359        return @ssl_params
 360      end
 361 
 362      def POP3.use_ssl?
 363        return !@ssl_params.nil?
 364      end
 365 
 366      def POP3.verify
 367        return @ssl_params[:verify_mode]
 368      end
 369 
 370      def POP3.certs
 371        return @ssl_params[:ca_file] || @ssl_params[:ca_path]
 372      end
 373 
 374      #
 375      # Session management
 376      #
 377 
 378      # Creates a new POP3 object and open the connection.  Equivalent to 
 379      #
 380      #   Net::POP3.new(address, port, isapop).start(account, password)
 381      #
 382      # If +block+ is provided, yields the newly-opened POP3 object to it,
 383      # and automatically closes it at the end of the session.
 384      #
 385      # === Example
 386      #
 387      #    Net::POP3.start(addr, port, account, password) do |pop|
 388      #      pop.each_mail do |m|
 389      #        file.write m.pop
 390      #        m.delete
 391      #      end
 392      #    end
 393      #
 394      def POP3.start(address, port = nil,
 395                     account = nil, password = nil,
 396                     isapop = false, &block)   # :yield: pop
 397        new(address, port, isapop).start(account, password, &block)
 398      end
 399      
 400      # Creates a new POP3 object.
 401      #
 402      # +address+ is the hostname or ip address of your POP3 server.
 403      #
 404      # The optional +port+ is the port to connect to.
 405      #
 406      # The optional +isapop+ specifies whether this connection is going
 407      # to use APOP authentication; it defaults to +false+.
 408      #
 409      # This method does *not* open the TCP connection.
 410      def initialize(addr, port = nil, isapop = false)
 411        @address = addr
 412        @ssl_params = POP3.ssl_params
 413        @port = port
 414        @apop = isapop
 415        
 416        @command = nil
 417        @socket = nil
 418        @started = false
 419        @open_timeout = 30
 420        @read_timeout = 60
 421        @debug_output = nil
 422 
 423        @mails = nil
 424        @n_mails = nil
 425        @n_bytes = nil
 426      end
 427 
 428      # Does this instance use APOP authentication?
 429      def apop?
 430        @apop
 431      end
 432 
 433      # does this instance use SSL?
 434      def use_ssl?
 435        return !@ssl_params.nil?
 436      end
 437     
 438      # call-seq:
 439      #    Net::POP#enable_ssl(params = {})
 440      #
 441      # Enables SSL for this instance.  Must be called before the connection is
 442      # established to have any effect.
 443      # +params[:port]+ is port to establish the SSL connection on; Defaults to 995.
 444      # +params+ (except :port) is passed to OpenSSL::SSLContext#set_params.
 445      def enable_ssl(verify_or_params = {}, certs = nil, port = nil)
 446        begin
 447          @ssl_params = verify_or_params.to_hash.dup
 448          @port = @ssl_params.delete(:port) || @port
 449        rescue NoMethodError
 450          @ssl_params = POP3.create_ssl_params(verify_or_params, certs)
 451          @port = port || @port
 452        end
 453      end
 454      
 455      def disable_ssl
 456        @ssl_params = nil
 457      end
 458 
 459      # Provide human-readable stringification of class state.
 460      def inspect
 461        "#<#{self.class} #{@address}:#{@port} open=#{@started}>"
 462      end
 463 
 464      # *WARNING*: This method causes a serious security hole.
 465      # Use this method only for debugging.
 466      #
 467      # Set an output stream for debugging.
 468      #
 469      # === Example
 470      #
 471      #   pop = Net::POP.new(addr, port)
 472      #   pop.set_debug_output $stderr
 473      #   pop.start(account, passwd) do |pop|
 474      #     ....
 475      #   end
 476      #
 477      def set_debug_output(arg)
 478        @debug_output = arg
 479      end
 480 
 481      # The address to connect to.
 482      attr_reader :address
 483 
 484      # The port number to connect to.
 485      def port
 486        return @port || (use_ssl? ? POP3.default_pop3s_port : POP3.default_pop3_port)
 487      end
 488 
 489      # Seconds to wait until a connection is opened.
 490      # If the POP3 object cannot open a connection within this time,
 491      # it raises a TimeoutError exception.
 492      attr_accessor :open_timeout
 493 
 494      # Seconds to wait until reading one block (by one read(1) call).
 495      # If the POP3 object cannot complete a read() within this time,
 496      # it raises a TimeoutError exception.
 497      attr_reader :read_timeout
 498 
 499      # Set the read timeout.
 500      def read_timeout=(sec)
 501        @command.socket.read_timeout = sec if @command
 502        @read_timeout = sec
 503      end
 504 
 505      # +true+ if the POP3 session has started.
 506      def started?
 507        @started
 508      end
 509 
 510      alias active? started?   #:nodoc: obsolete
 511 
 512      # Starts a POP3 session.
 513      #
 514      # When called with block, gives a POP3 object to the block and
 515      # closes the session after block call finishes.
 516      #
 517      # This method raises a POPAuthenticationError if authentication fails.
 518      def start(account, password) # :yield: pop
 519        raise IOError, 'POP session already started' if @started
 520        if block_given?
 521          begin
 522            do_start account, password
 523            return yield(self)
 524          ensure
 525            do_finish
 526          end
 527        else
 528          do_start account, password
 529          return self
 530        end
 531      end
 532 
 533      def do_start(account, password)
 534        s = timeout(@open_timeout) { TCPSocket.open(@address, port) }
 535        if use_ssl?
 536          raise 'openssl library not installed' unless defined?(OpenSSL)
 537          context = OpenSSL::SSL::SSLContext.new
 538          context.set_params(@ssl_params)
 539          s = OpenSSL::SSL::SSLSocket.new(s, context)
 540          s.sync_close = true
 541          s.connect
 542          if context.verify_mode != OpenSSL::SSL::VERIFY_NONE
 543            s.post_connection_check(@address)
 544          end
 545        end
 546        @socket = InternetMessageIO.new(s)
 547        logging "POP session started: #{@address}:#{@port} (#{@apop ? 'APOP' : 'POP'})"
 548        @socket.read_timeout = @read_timeout
 549        @socket.debug_output = @debug_output
 550        on_connect
 551        @command = POP3Command.new(@socket)
 552        if apop?
 553          @command.apop account, password
 554        else
 555          @command.auth account, password
 556        end
 557        @started = true
 558      ensure
 559        # Authentication failed, clean up connection.
 560        unless @started
 561          s.close if s and not s.closed?
 562          @socket = nil
 563          @command = nil
 564        end
 565      end
 566      private :do_start
 567 
 568      def on_connect
 569      end
 570      private :on_connect
 571 
 572      # Finishes a POP3 session and closes TCP connection.
 573      def finish
 574        raise IOError, 'POP session not yet started' unless started?
 575        do_finish
 576      end
 577 
 578      def do_finish
 579        @mails = nil
 580        @n_mails = nil
 581        @n_bytes = nil
 582        @command.quit if @command
 583      ensure
 584        @started = false
 585        @command = nil
 586        @socket.close if @socket and not @socket.closed?
 587        @socket = nil
 588      end
 589      private :do_finish
 590 
 591      def command
 592        raise IOError, 'POP session not opened yet' \
 593                                        if not @socket or @socket.closed?
 594        @command
 595      end
 596      private :command
 597 
 598      #
 599      # POP protocol wrapper
 600      #
 601 
 602      # Returns the number of messages on the POP server.
 603      def n_mails
 604        return @n_mails if @n_mails
 605        @n_mails, @n_bytes = command().stat
 606        @n_mails
 607      end
 608 
 609      # Returns the total size in bytes of all the messages on the POP server.
 610      def n_bytes
 611        return @n_bytes if @n_bytes
 612        @n_mails, @n_bytes = command().stat
 613        @n_bytes
 614      end
 615 
 616      # Returns an array of Net::POPMail objects, representing all the
 617      # messages on the server.  This array is renewed when the session
 618      # restarts; otherwise, it is fetched from the server the first time
 619      # this method is called (directly or indirectly) and cached.
 620      #
 621      # This method raises a POPError if an error occurs.
 622      def mails
 623        return @mails.dup if @mails
 624        if n_mails() == 0
 625          # some popd raises error for LIST on the empty mailbox.
 626          @mails = []
 627          return []
 628        end
 629 
 630        @mails = command().list.map {|num, size|
 631          POPMail.new(num, size, self, command())
 632        }
 633        @mails.dup
 634      end
 635 
 636      # Yields each message to the passed-in block in turn.
 637      # Equivalent to:
 638      # 
 639      #   pop3.mails.each do |popmail|
 640      #     ....
 641      #   end
 642      #
 643      # This method raises a POPError if an error occurs.
 644      def each_mail(&block)  # :yield: message
 645        mails().each(&block)
 646      end
 647 
 648      alias each each_mail
 649 
 650      # Deletes all messages on the server.
 651      #
 652      # If called with a block, yields each message in turn before deleting it.
 653      #
 654      # === Example
 655      #
 656      #     n = 1
 657      #     pop.delete_all do |m|
 658      #       File.open("inbox/#{n}") do |f|
 659      #         f.write m.pop
 660      #       end
 661      #       n += 1
 662      #     end
 663      #
 664      # This method raises a POPError if an error occurs.
 665      #
 666      def delete_all # :yield: message
 667        mails().each do |m|
 668          yield m if block_given?
 669          m.delete unless m.deleted?
 670        end
 671      end
 672 
 673      # Resets the session.  This clears all "deleted" marks from messages.
 674      #
 675      # This method raises a POPError if an error occurs.
 676      def reset
 677        command().rset
 678        mails().each do |m|
 679          m.instance_eval {
 680            @deleted = false
 681          }
 682        end
 683      end
 684 
 685      def set_all_uids   #:nodoc: internal use only (called from POPMail#uidl)
 686        uidl = command().uidl
 687        @mails.each {|m| m.uid = uidl[m.number] }
 688      end
 689 
 690      def logging(msg)
 691        @debug_output << msg + "\n" if @debug_output
 692      end
 693 
 694    end   # class POP3
 695 
 696    # class aliases
 697    POP = POP3
 698    POPSession  = POP3
 699    POP3Session = POP3
 700 
 701    #
 702    # This class is equivalent to POP3, except that it uses APOP authentication.
 703    #
 704    class APOP < POP3
 705      # Always returns true.
 706      def apop?
 707        true
 708      end
 709    end
 710 
 711    # class aliases
 712    APOPSession = APOP
 713 
 714    #
 715    # This class represents a message which exists on the POP server.
 716    # Instances of this class are created by the POP3 class; they should
 717    # not be directly created by the user.
 718    #
 719    class POPMail
 720 
 721      def initialize(num, len, pop, cmd)   #:nodoc:
 722        @number = num
 723        @length = len
 724        @pop = pop
 725        @command = cmd
 726        @deleted = false
 727        @uid = nil
 728      end
 729 
 730      # The sequence number of the message on the server.
 731      attr_reader :number
 732 
 733      # The length of the message in octets.
 734      attr_reader :length
 735      alias size length
 736 
 737      # Provide human-readable stringification of class state.
 738      def inspect
 739        "#<#{self.class} #{@number}#{@deleted ? ' deleted' : ''}>"
 740      end
 741 
 742      #
 743      # This method fetches the message.  If called with a block, the
 744      # message is yielded to the block one chunk at a time.  If called
 745      # without a block, the message is returned as a String.  The optional 
 746      # +dest+ argument will be prepended to the returned String; this
 747      # argument is essentially obsolete.
 748      #
 749      # === Example without block
 750      #
 751      #     POP3.start('pop.example.com', 110,
 752      #                'YourAccount, 'YourPassword') do |pop|
 753      #       n = 1
 754      #       pop.mails.each do |popmail|
 755      #         File.open("inbox/#{n}", 'w') do |f|
 756      #           f.write popmail.pop              
 757      #         end
 758      #         popmail.delete
 759      #         n += 1
 760      #       end
 761      #     end
 762      #
 763      # === Example with block
 764      #
 765      #     POP3.start('pop.example.com', 110,
 766      #                'YourAccount, 'YourPassword') do |pop|
 767      #       n = 1
 768      #       pop.mails.each do |popmail|
 769      #         File.open("inbox/#{n}", 'w') do |f|
 770      #           popmail.pop do |chunk|            ####
 771      #             f.write chunk
 772      #           end
 773      #         end
 774      #         n += 1
 775      #       end
 776      #     end
 777      #
 778      # This method raises a POPError if an error occurs.
 779      #
 780      def pop( dest = '', &block ) # :yield: message_chunk
 781        if block_given?
 782          @command.retr(@number, &block)
 783          nil
 784        else
 785          @command.retr(@number) do |chunk|
 786            dest << chunk
 787          end
 788          dest
 789        end
 790      end
 791 
 792      alias all pop    #:nodoc: obsolete
 793      alias mail pop   #:nodoc: obsolete
 794 
 795      # Fetches the message header and +lines+ lines of body. 
 796      #
 797      # The optional +dest+ argument is obsolete.
 798      #
 799      # This method raises a POPError if an error occurs.
 800      def top(lines, dest = '')
 801        @command.top(@number, lines) do |chunk|
 802          dest << chunk
 803        end
 804        dest
 805      end
 806 
 807      # Fetches the message header.     
 808      #
 809      # The optional +dest+ argument is obsolete.
 810      #
 811      # This method raises a POPError if an error occurs.
 812      def header(dest = '')
 813        top(0, dest)
 814      end
 815 
 816      # Marks a message for deletion on the server.  Deletion does not
 817      # actually occur until the end of the session; deletion may be
 818      # cancelled for _all_ marked messages by calling POP3#reset().
 819      #
 820      # This method raises a POPError if an error occurs.
 821      #
 822      # === Example
 823      #
 824      #     POP3.start('pop.example.com', 110,
 825      #                'YourAccount, 'YourPassword') do |pop|
 826      #       n = 1
 827      #       pop.mails.each do |popmail|
 828      #         File.open("inbox/#{n}", 'w') do |f|
 829      #           f.write popmail.pop
 830      #         end
 831      #         popmail.delete         ####
 832      #         n += 1
 833      #       end
 834      #     end
 835      #
 836      def delete
 837        @command.dele @number
 838        @deleted = true
 839      end
 840 
 841      alias delete! delete    #:nodoc: obsolete
 842 
 843      # True if the mail has been deleted.
 844      def deleted?
 845        @deleted
 846      end
 847 
 848      # Returns the unique-id of the message.
 849      # Normally the unique-id is a hash string of the message.
 850      #
 851      # This method raises a POPError if an error occurs.
 852      def unique_id
 853        return @uid if @uid
 854        @pop.set_all_uids
 855        @uid
 856      end
 857 
 858      alias uidl unique_id
 859 
 860      def uid=(uid)   #:nodoc: internal use only
 861        @uid = uid
 862      end
 863 
 864    end   # class POPMail
 865 
 866 
 867    class POP3Command   #:nodoc: internal use only
 868 
 869      def initialize(sock)
 870        @socket = sock
 871        @error_occured = false
 872        res = check_response(critical { recv_response() })
 873        @apop_stamp = res.slice(/<[!-~]+@[!-~]+>/)
 874      end
 875 
 876      def inspect
 877        "#<#{self.class} socket=#{@socket}>"
 878      end
 879 
 880      def auth(account, password)
 881        check_response_auth(critical {
 882          check_response_auth(get_response('USER %s', account))
 883          get_response('PASS %s', password)
 884        })
 885      end
 886 
 887      def apop(account, password)
 888        raise POPAuthenticationError, 'not APOP server; cannot login' \
 889                                                        unless @apop_stamp
 890        check_response_auth(critical {
 891          get_response('APOP %s %s',
 892                       account,
 893                       Digest::MD5.hexdigest(@apop_stamp + password))
 894        })
 895      end
 896 
 897      def list
 898        critical {
 899          getok 'LIST'
 900          list = []
 901          @socket.each_list_item do |line|
 902            m = /\A(\d+)[ \t]+(\d+)/.match(line) or
 903                    raise POPBadResponse, "bad response: #{line}"
 904            list.push  [m[1].to_i, m[2].to_i]
 905          end
 906          return list
 907        }
 908      end
 909 
 910      def stat
 911        res = check_response(critical { get_response('STAT') })
 912        m = /\A\+OK\s+(\d+)\s+(\d+)/.match(res) or
 913                raise POPBadResponse, "wrong response format: #{res}"
 914        [m[1].to_i, m[2].to_i]
 915      end
 916 
 917      def rset
 918        check_response(critical { get_response('RSET') })
 919      end
 920 
 921      def top(num, lines = 0, &block)
 922        critical {
 923          getok('TOP %d %d', num, lines)
 924          @socket.each_message_chunk(&block)
 925        }
 926      end
 927 
 928      def retr(num, &block)
 929        critical {
 930          getok('RETR %d', num)
 931          @socket.each_message_chunk(&block)
 932        }
 933      end
 934      
 935      def dele(num)
 936        check_response(critical { get_response('DELE %d', num) })
 937      end
 938 
 939      def uidl(num = nil)
 940        if num
 941          res = check_response(critical { get_response('UIDL %d', num) })
 942          return res.split(/ /)[1]
 943        else
 944          critical {
 945            getok('UIDL')
 946            table = {}
 947            @socket.each_list_item do |line|
 948              num, uid = line.split
 949              table[num.to_i] = uid
 950            end
 951            return table
 952          }
 953        end
 954      end
 955 
 956      def quit
 957        check_response(critical { get_response('QUIT') })
 958      end
 959 
 960      private
 961 
 962      def getok(fmt, *fargs)
 963        @socket.writeline sprintf(fmt, *fargs)
 964        check_response(recv_response())
 965      end
 966 
 967      def get_response(fmt, *fargs)
 968        @socket.writeline sprintf(fmt, *fargs)
 969        recv_response()
 970      end
 971 
 972      def recv_response
 973        @socket.readline
 974      end
 975 
 976      def check_response(res)
 977        raise POPError, res unless /\A\+OK/i =~ res
 978        res
 979      end
 980 
 981      def check_response_auth(res)
 982        raise POPAuthenticationError, res unless /\A\+OK/i =~ res
 983        res
 984      end
 985 
 986      def critical
 987        return '+OK dummy ok response' if @error_occured
 988        begin
 989          return yield()
 990        rescue Exception
 991          @error_occured = true
 992          raise
 993        end
 994      end
 995 
 996    end   # class POP3Command
 997 
 998  end   # module Net