PageRenderTime 171ms CodeModel.GetById 122ms app.highlight 44ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/Ruby/lib/ruby/1.8/net/pop.rb

http://github.com/agross/netopenspace
Ruby | 998 lines | 440 code | 106 blank | 452 comment | 29 complexity | ebe51763396b218524a9287929667e72 MD5 | raw file
  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
 23require 'net/protocol'
 24require 'digest/md5'
 25require 'timeout'
 26
 27begin
 28  require "openssl/ssl"
 29rescue LoadError
 30end
 31
 32module 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
998end   # module Net