3 # Copyright (c) 1999-2007 Yukihiro Matsumoto.
5 # Copyright (c) 1999-2007 Minero Aoki.
7 # Written & maintained by Minero Aoki <aamine@loveruby.net>.
9 # Documented by William Webber and Minero Aoki.
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.
15 # NOTE: You can find Japanese version of this document at:
16 # http://www.ruby-lang.org/ja/man/html/net_pop.html
20 # See Net::POP3 for documentation.
23 require 'net/protocol'
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
47 # == What is This Library?
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).
55 # === Retrieving Messages
57 # This example retrieves messages from the server and deletes them
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
67 # pop = Net::POP3.new('pop.example.com')
68 # pop.start('YourAccount', 'YourPassword') # (1)
73 # pop.each_mail do |m| # or "pop.mails.each ..." # (2)
74 # File.open("inbox/#{i}", 'w') do |f|
80 # puts "#{pop.mails.size} mails popped."
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.
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.
96 # Net::POP3.start('pop.example.com', 110,
97 # 'YourAccount', 'YourPassword') do |pop|
102 # pop.each_mail do |m| # or "pop.mails.each ..."
103 # File.open("inbox/#{i}", 'w') do |f|
109 # puts "#{pop.mails.size} mails popped."
113 # POP3#delete_all is an alternative for #each_mail and #delete.
117 # Net::POP3.start('pop.example.com', 110,
118 # 'YourAccount', 'YourPassword') do |pop|
119 # if pop.mails.empty?
123 # pop.delete_all do |m|
124 # File.open("inbox/#{i}", 'w') do |f|
132 # And here is an even shorter example.
137 # Net::POP3.delete_all('pop.example.com', 110,
138 # 'YourAccount', 'YourPassword') do |m|
139 # File.open("inbox/#{i}", 'w') do |f|
145 # === Memory Space Issues
147 # All the examples above get each message as one big string.
148 # This example avoids this.
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.
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:
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.
177 # === Fetch Only Selected Mail Using 'UIDL' POP Command
179 # If your POP server provides UIDL functionality,
180 # you can grab only selected mails from the POP server.
183 # def need_pop?( id )
184 # # determine if we need pop this mail...
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)
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.
197 class POP3 < Protocol
199 Revision = %q$Revision$.split[1]
205 def POP3.default_port
209 # The default port for POP3 connections, port 110
210 def POP3.default_pop3_port
214 # The default port for POP3S connections, port 995
215 def POP3.default_pop3s_port
219 def POP3.socket_type #:nodoc: obsolete
220 Net::InternetMessageIO
227 # Returns the APOP class if +isapop+ is true; otherwise, returns
228 # the POP class. For example:
231 # pop = Net::POP3::APOP($is_apop).new(addr, port)
234 # Net::POP3::APOP($is_apop).start(addr, port) do |pop|
238 def POP3.APOP(isapop)
242 # Starts a POP3 session and iterates over each POPMail object,
243 # yielding it to the +block+.
244 # This method is equivalent to:
246 # Net::POP3.start(address, port, account, password) do |pop|
247 # pop.each_mail do |m|
252 # This method raises a POPAuthenticationError if authentication fails.
256 # Net::POP3.foreach('pop.example.com', 110,
257 # 'YourAccount', 'YourPassword') do |m|
259 # m.delete if $DELETE
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)
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
274 # This method raises a POPAuthenticationError if authentication fails.
278 # Net::POP3.delete_all('pop.example.com', 110,
279 # 'YourAccount', 'YourPassword') do |m|
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)
291 # Opens a POP3 session, attempts authentication, and quits.
293 # This method raises POPAuthenticationError if authentication fails.
295 # === Example: normal POP3
297 # Net::POP3.auth_only('pop.example.com', 110,
298 # 'YourAccount', 'YourPassword')
302 # Net::POP3.auth_only('pop.example.com', 110,
303 # 'YourAccount', 'YourPassword', true)
305 def POP3.auth_only(address, port = nil,
306 account = nil, password = nil,
308 new(address, port, isapop).auth_only account, password
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) {
328 # Net::POP.enable_ssl(params = {})
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)
336 def POP3.create_ssl_params(verify_or_params = {}, certs = nil)
338 params = verify_or_params.to_hash
341 params[:verify_mode] = verify_or_params
344 params[:ca_file] = certs
345 elsif File.directory?(certs)
346 params[:ca_path] = certs
353 # Disable SSL for all new instances.
363 return !@ssl_params.nil?
367 return @ssl_params[:verify_mode]
371 return @ssl_params[:ca_file] || @ssl_params[:ca_path]
378 # Creates a new POP3 object and open the connection. Equivalent to
380 # Net::POP3.new(address, port, isapop).start(account, password)
382 # If +block+ is provided, yields the newly-opened POP3 object to it,
383 # and automatically closes it at the end of the session.
387 # Net::POP3.start(addr, port, account, password) do |pop|
388 # pop.each_mail do |m|
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)
400 # Creates a new POP3 object.
402 # +address+ is the hostname or ip address of your POP3 server.
404 # The optional +port+ is the port to connect to.
406 # The optional +isapop+ specifies whether this connection is going
407 # to use APOP authentication; it defaults to +false+.
409 # This method does *not* open the TCP connection.
410 def initialize(addr, port = nil, isapop = false)
412 @ssl_params = POP3.ssl_params
428 # Does this instance use APOP authentication?
433 # does this instance use SSL?
435 return !@ssl_params.nil?
439 # Net::POP#enable_ssl(params = {})
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)
447 @ssl_params = verify_or_params.to_hash.dup
448 @port = @ssl_params.delete(:port) || @port
450 @ssl_params = POP3.create_ssl_params(verify_or_params, certs)
451 @port = port || @port
459 # Provide human-readable stringification of class state.
461 "#<#{self.class} #{@address}:#{@port} open=#{@started}>"
464 # *WARNING*: This method causes a serious security hole.
465 # Use this method only for debugging.
467 # Set an output stream for debugging.
471 # pop = Net::POP.new(addr, port)
472 # pop.set_debug_output $stderr
473 # pop.start(account, passwd) do |pop|
477 def set_debug_output(arg)
481 # The address to connect to.
484 # The port number to connect to.
486 return @port || (use_ssl? ? POP3.default_pop3s_port : POP3.default_pop3_port)
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
505 # +true+ if the POP3 session has started.
510 alias active? started? #:nodoc: obsolete
512 # Starts a POP3 session.
514 # When called with block, gives a POP3 object to the block and
515 # closes the session after block call finishes.
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
522 do_start account, password
528 do_start account, password
533 def do_start(account, password)
534 s = timeout(@open_timeout) { TCPSocket.open(@address, port) }
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)
542 if context.verify_mode != OpenSSL::SSL::VERIFY_NONE
543 s.post_connection_check(@address)
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
551 @command = POP3Command.new(@socket)
553 @command.apop account, password
555 @command.auth account, password
559 # Authentication failed, clean up connection.
561 s.close if s and not s.closed?
572 # Finishes a POP3 session and closes TCP connection.
574 raise IOError, 'POP session not yet started' unless started?
582 @command.quit if @command
586 @socket.close if @socket and not @socket.closed?
592 raise IOError, 'POP session not opened yet' \
593 if not @socket or @socket.closed?
599 # POP protocol wrapper
602 # Returns the number of messages on the POP server.
604 return @n_mails if @n_mails
605 @n_mails, @n_bytes = command().stat
609 # Returns the total size in bytes of all the messages on the POP server.
611 return @n_bytes if @n_bytes
612 @n_mails, @n_bytes = command().stat
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.
621 # This method raises a POPError if an error occurs.
623 return @mails.dup if @mails
625 # some popd raises error for LIST on the empty mailbox.
630 @mails = command().list.map {|num, size|
631 POPMail.new(num, size, self, command())
636 # Yields each message to the passed-in block in turn.
639 # pop3.mails.each do |popmail|
643 # This method raises a POPError if an error occurs.
644 def each_mail(&block) # :yield: message
650 # Deletes all messages on the server.
652 # If called with a block, yields each message in turn before deleting it.
657 # pop.delete_all do |m|
658 # File.open("inbox/#{n}") do |f|
664 # This method raises a POPError if an error occurs.
666 def delete_all # :yield: message
668 yield m if block_given?
669 m.delete unless m.deleted?
673 # Resets the session. This clears all "deleted" marks from messages.
675 # This method raises a POPError if an error occurs.
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] }
691 @debug_output << msg + "\n" if @debug_output
702 # This class is equivalent to POP3, except that it uses APOP authentication.
705 # Always returns true.
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.
721 def initialize(num, len, pop, cmd) #:nodoc:
730 # The sequence number of the message on the server.
733 # The length of the message in octets.
737 # Provide human-readable stringification of class state.
739 "#<#{self.class} #{@number}#{@deleted ? ' deleted' : ''}>"
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.
749 # === Example without block
751 # POP3.start('pop.example.com', 110,
752 # 'YourAccount, 'YourPassword') do |pop|
754 # pop.mails.each do |popmail|
755 # File.open("inbox/#{n}", 'w') do |f|
756 # f.write popmail.pop
763 # === Example with block
765 # POP3.start('pop.example.com', 110,
766 # 'YourAccount, 'YourPassword') do |pop|
768 # pop.mails.each do |popmail|
769 # File.open("inbox/#{n}", 'w') do |f|
770 # popmail.pop do |chunk| ####
778 # This method raises a POPError if an error occurs.
780 def pop( dest = '', &block ) # :yield: message_chunk
782 @command.retr(@number, &block)
785 @command.retr(@number) do |chunk|
792 alias all pop #:nodoc: obsolete
793 alias mail pop #:nodoc: obsolete
795 # Fetches the message header and +lines+ lines of body.
797 # The optional +dest+ argument is obsolete.
799 # This method raises a POPError if an error occurs.
800 def top(lines, dest = '')
801 @command.top(@number, lines) do |chunk|
807 # Fetches the message header.
809 # The optional +dest+ argument is obsolete.
811 # This method raises a POPError if an error occurs.
812 def header(dest = '')
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().
820 # This method raises a POPError if an error occurs.
824 # POP3.start('pop.example.com', 110,
825 # 'YourAccount, 'YourPassword') do |pop|
827 # pop.mails.each do |popmail|
828 # File.open("inbox/#{n}", 'w') do |f|
829 # f.write popmail.pop
831 # popmail.delete ####
837 @command.dele @number
841 alias delete! delete #:nodoc: obsolete
843 # True if the mail has been deleted.
848 # Returns the unique-id of the message.
849 # Normally the unique-id is a hash string of the message.
851 # This method raises a POPError if an error occurs.
860 def uid=(uid) #:nodoc: internal use only
867 class POP3Command #:nodoc: internal use only
871 @error_occured = false
872 res = check_response(critical { recv_response() })
873 @apop_stamp = res.slice(/<.+>/)
879 "#<#{self.class} socket=#{@socket}>"
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)
889 def apop(account, password)
890 raise POPAuthenticationError, 'not APOP server; cannot login' \
892 check_response_auth(critical {
893 get_response('APOP %s %s',
895 Digest::MD5.hexdigest(@apop_stamp + password))
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]
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]
920 check_response(critical { get_response('RSET') })
923 def top(num, lines = 0, &block)
925 getok('TOP %d %d', num, lines)
926 @socket.each_message_chunk(&block)
930 def retr(num, &block)
932 getok('RETR %d', num)
933 @socket.each_message_chunk(&block)
938 check_response(critical { get_response('DELE %d', num) })
943 res = check_response(critical { get_response('UIDL %d', num) })
944 return res.split(/ /)[1]
949 @socket.each_list_item do |line|
950 num, uid = line.split
951 table[num.to_i] = uid
959 check_response(critical { get_response('QUIT') })
964 def getok(fmt, *fargs)
965 @socket.writeline sprintf(fmt, *fargs)
966 check_response(recv_response())
969 def get_response(fmt, *fargs)
970 @socket.writeline sprintf(fmt, *fargs)
978 def check_response(res)
979 raise POPError, res unless /\A\+OK/i =~ res
983 def check_response_auth(res)
984 raise POPAuthenticationError, res unless /\A\+OK/i =~ res
989 return '+OK dummy ok response' if @error_occured
993 @error_occured = true
998 end # class POP3Command