* transcode_data.h (rb_transcoder_stateful_type_t): defined.
[ruby-svn.git] / lib / net / pop.rb
bloba8e99872363ff94e82bec9810696ccbb92b30e1e
1 # = net/pop.rb
3 # Copyright (c) 1999-2007 Yukihiro Matsumoto.
5 # Copyright (c) 1999-2007 Minero Aoki.
6
7 # Written & maintained by Minero Aoki <aamine@loveruby.net>.
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$
19
20 # See Net::POP3 for documentation.
23 require 'net/protocol'
24 require 'digest/md5'
25 require 'timeout'
27 begin
28   require "openssl/ssl"
29 rescue LoadError
30 end
32 module Net
34   # Non-authentication POP3 protocol error
35   # (reply code "-ERR", except authentication).
36   class POPError < ProtocolError; end
38   # POP3 authentication error.
39   class POPAuthenticationError < ProtoAuthError; end
41   # Unexpected response from the server.
42   class POPBadResponse < POPError; end
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
199     Revision = %q$Revision$.split[1]
201     #
202     # Class Parameters
203     #
205     def POP3.default_port
206       default_pop3_port()
207     end
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
219     def POP3.socket_type   #:nodoc: obsolete
220       Net::InternetMessageIO
221     end
223     #
224     # Utilities
225     #
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
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
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
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
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
321     #
322     # SSL
323     #
325     @ssl_params = nil
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
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
353     # Disable SSL for all new instances.
354     def POP3.disable_ssl
355       @ssl_params = nil
356     end
358     def POP3.ssl_params
359       return @ssl_params
360     end
362     def POP3.use_ssl?
363       return !@ssl_params.nil?
364     end
366     def POP3.verify
367       return @ssl_params[:verify_mode]
368     end
370     def POP3.certs
371       return @ssl_params[:ca_file] || @ssl_params[:ca_path]
372     end
374     #
375     # Session management
376     #
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
423       @mails = nil
424       @n_mails = nil
425       @n_bytes = nil
426     end
428     # Does this instance use APOP authentication?
429     def apop?
430       @apop
431     end
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
459     # Provide human-readable stringification of class state.
460     def inspect
461       "#<#{self.class} #{@address}:#{@port} open=#{@started}>"
462     end
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
481     # The address to connect to.
482     attr_reader :address
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
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
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
499     # Set the read timeout.
500     def read_timeout=(sec)
501       @command.socket.read_timeout = sec if @command
502       @read_timeout = sec
503     end
505     # +true+ if the POP3 session has started.
506     def started?
507       @started
508     end
510     alias active? started?   #:nodoc: obsolete
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
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
568     def on_connect
569     end
570     private :on_connect
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
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
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
598     #
599     # POP protocol wrapper
600     #
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
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
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
630       @mails = command().list.map {|num, size|
631         POPMail.new(num, size, self, command())
632       }
633       @mails.dup
634     end
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
648     alias each each_mail
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
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
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
690     def logging(msg)
691       @debug_output << msg + "\n" if @debug_output
692     end
694   end   # class POP3
696   # class aliases
697   POP = POP3
698   POPSession  = POP3
699   POP3Session = POP3
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
711   # class aliases
712   APOPSession = APOP
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
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
730     # The sequence number of the message on the server.
731     attr_reader :number
733     # The length of the message in octets.
734     attr_reader :length
735     alias size length
737     # Provide human-readable stringification of class state.
738     def inspect
739       "#<#{self.class} #{@number}#{@deleted ? ' deleted' : ''}>"
740     end
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
792     alias all pop    #:nodoc: obsolete
793     alias mail pop   #:nodoc: obsolete
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
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
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
841     alias delete! delete    #:nodoc: obsolete
843     # True if the mail has been deleted.
844     def deleted?
845       @deleted
846     end
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
858     alias uidl unique_id
860     def uid=(uid)   #:nodoc: internal use only
861       @uid = uid
862     end
864   end   # class POPMail
867   class POP3Command   #:nodoc: internal use only
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
876     attr_reader :socket
878     def inspect
879       "#<#{self.class} socket=#{@socket}>"
880     end
882     def auth(account, password)
883       check_response_auth(critical {
884         check_response_auth(get_response('USER %s', account))
885         get_response('PASS %s', password)
886       })
887     end
889     def apop(account, password)
890       raise POPAuthenticationError, 'not APOP server; cannot login' \
891                                                       unless @apop_stamp
892       check_response_auth(critical {
893         get_response('APOP %s %s',
894                      account,
895                      Digest::MD5.hexdigest(@apop_stamp + password))
896       })
897     end
899     def list
900       critical {
901         getok 'LIST'
902         list = []
903         @socket.each_list_item do |line|
904           m = /\A(\d+)[ \t]+(\d+)/.match(line) or
905                   raise POPBadResponse, "bad response: #{line}"
906           list.push  [m[1].to_i, m[2].to_i]
907         end
908         return list
909       }
910     end
912     def stat
913       res = check_response(critical { get_response('STAT') })
914       m = /\A\+OK\s+(\d+)\s+(\d+)/.match(res) or
915               raise POPBadResponse, "wrong response format: #{res}"
916       [m[1].to_i, m[2].to_i]
917     end
919     def rset
920       check_response(critical { get_response('RSET') })
921     end
923     def top(num, lines = 0, &block)
924       critical {
925         getok('TOP %d %d', num, lines)
926         @socket.each_message_chunk(&block)
927       }
928     end
930     def retr(num, &block)
931       critical {
932         getok('RETR %d', num)
933         @socket.each_message_chunk(&block)
934       }
935     end
936     
937     def dele(num)
938       check_response(critical { get_response('DELE %d', num) })
939     end
941     def uidl(num = nil)
942       if num
943         res = check_response(critical { get_response('UIDL %d', num) })
944         return res.split(/ /)[1]
945       else
946         critical {
947           getok('UIDL')
948           table = {}
949           @socket.each_list_item do |line|
950             num, uid = line.split
951             table[num.to_i] = uid
952           end
953           return table
954         }
955       end
956     end
958     def quit
959       check_response(critical { get_response('QUIT') })
960     end
962     private
964     def getok(fmt, *fargs)
965       @socket.writeline sprintf(fmt, *fargs)
966       check_response(recv_response())
967     end
969     def get_response(fmt, *fargs)
970       @socket.writeline sprintf(fmt, *fargs)
971       recv_response()
972     end
974     def recv_response
975       @socket.readline
976     end
978     def check_response(res)
979       raise POPError, res unless /\A\+OK/i =~ res
980       res
981     end
983     def check_response_auth(res)
984       raise POPAuthenticationError, res unless /\A\+OK/i =~ res
985       res
986     end
988     def critical
989       return '+OK dummy ok response' if @error_occured
990       begin
991         return yield()
992       rescue Exception
993         @error_occured = true
994         raise
995       end
996     end
998   end   # class POP3Command
1000 end   # module Net