3
# Copyright (c) 1999-2003 Yukihiro Matsumoto.
5
# Copyright (c) 1999-2003 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 or GNU General Public License.
15
# NOTE: You can find Japanese version of this document in
16
# the doc/net directory of the standard ruby interpreter package.
18
# $Id: pop.rb 11708 2007-02-12 23:01:19Z shyouhei $
20
# See Net::POP3 for documentation.
23
require 'net/protocol'
28
# Non-authentication POP3 protocol error
29
# (reply code "-ERR", except authentication).
30
class POPError < ProtocolError; end
32
# POP3 authentication error.
33
class POPAuthenticationError < ProtoAuthError; end
35
# Unexpected response from the server.
36
class POPBadResponse < POPError; end
41
# == What is This Library?
43
# This library provides functionality for retrieving
44
# email via POP3, the Post Office Protocol version 3. For details
45
# of POP3, see [RFC1939] (http://www.ietf.org/rfc/rfc1939.txt).
49
# === Retrieving Messages
51
# This example retrieves messages from the server and deletes them
54
# Messages are written to files named 'inbox/1', 'inbox/2', ....
55
# Replace 'pop.example.com' with your POP3 server address, and
56
# 'YourAccount' and 'YourPassword' with the appropriate account
61
# pop = Net::POP3.new('pop.example.com')
62
# pop.start('YourAccount', 'YourPassword') # (1)
67
# pop.each_mail do |m| # or "pop.mails.each ..." # (2)
68
# File.open("inbox/#{i}", 'w') do |f|
74
# puts "#{pop.mails.size} mails popped."
78
# 1. Call Net::POP3#start and start POP session.
79
# 2. Access messages by using POP3#each_mail and/or POP3#mails.
80
# 3. Close POP session by calling POP3#finish or use the block form of #start.
84
# The example above is very verbose. You can shorten the code by using
85
# some utility methods. First, the block form of Net::POP3.start can
86
# be used instead of POP3.new, POP3#start and POP3#finish.
90
# Net::POP3.start('pop.example.com', 110,
91
# 'YourAccount', 'YourPassword') do |pop|
96
# pop.each_mail do |m| # or "pop.mails.each ..."
97
# File.open("inbox/#{i}", 'w') do |f|
103
# puts "#{pop.mails.size} mails popped."
107
# POP3#delete_all is an alternative for #each_mail and #delete.
111
# Net::POP3.start('pop.example.com', 110,
112
# 'YourAccount', 'YourPassword') do |pop|
113
# if pop.mails.empty?
117
# pop.delete_all do |m|
118
# File.open("inbox/#{i}", 'w') do |f|
126
# And here is an even shorter example.
131
# Net::POP3.delete_all('pop.example.com', 110,
132
# 'YourAccount', 'YourPassword') do |m|
133
# File.open("inbox/#{i}", 'w') do |f|
139
# === Memory Space Issues
141
# All the examples above get each message as one big string.
142
# This example avoids this.
147
# Net::POP3.delete_all('pop.example.com', 110,
148
# 'YourAccount', 'YourPassword') do |m|
149
# File.open("inbox/#{i}", 'w') do |f|
150
# m.pop do |chunk| # get a message little by little.
159
# The net/pop library supports APOP authentication.
160
# To use APOP, use the Net::APOP class instead of the Net::POP3 class.
161
# You can use the utility method, Net::POP3.APOP(). For example:
165
# # Use APOP authentication if $isapop == true
166
# pop = Net::POP3.APOP($is_apop).new('apop.example.com', 110)
167
# pop.start(YourAccount', 'YourPassword') do |pop|
168
# # Rest of the code is the same.
171
# === Fetch Only Selected Mail Using 'UIDL' POP Command
173
# If your POP server provides UIDL functionality,
174
# you can grab only selected mails from the POP server.
177
# def need_pop?( id )
178
# # determine if we need pop this mail...
181
# Net::POP3.start('pop.example.com', 110,
182
# 'Your account', 'Your password') do |pop|
183
# pop.mails.select { |m| need_pop?(m.unique_id) }.each do |m|
184
# do_something(m.pop)
188
# The POPMail#unique_id() method returns the unique-id of the message as a
189
# String. Normally the unique-id is a hash of the message.
191
class POP3 < Protocol
193
Revision = %q$Revision: 11708 $.split[1]
199
# The default port for POP3 connections, port 110
200
def POP3.default_port
204
def POP3.socket_type #:nodoc: obsolete
205
Net::InternetMessageIO
212
# Returns the APOP class if +isapop+ is true; otherwise, returns
213
# the POP class. For example:
216
# pop = Net::POP3::APOP($is_apop).new(addr, port)
219
# Net::POP3::APOP($is_apop).start(addr, port) do |pop|
223
def POP3.APOP( isapop )
227
# Starts a POP3 session and iterates over each POPMail object,
228
# yielding it to the +block+.
229
# This method is equivalent to:
231
# Net::POP3.start(address, port, account, password) do |pop|
232
# pop.each_mail do |m|
237
# This method raises a POPAuthenticationError if authentication fails.
241
# Net::POP3.foreach('pop.example.com', 110,
242
# 'YourAccount', 'YourPassword') do |m|
244
# m.delete if $DELETE
247
def POP3.foreach( address, port = nil,
248
account = nil, password = nil,
249
isapop = false, &block ) # :yields: message
250
start(address, port, account, password, isapop) {|pop|
251
pop.each_mail(&block)
255
# Starts a POP3 session and deletes all messages on the server.
256
# If a block is given, each POPMail object is yielded to it before
259
# This method raises a POPAuthenticationError if authentication fails.
263
# Net::POP3.delete_all('pop.example.com', 110,
264
# 'YourAccount', 'YourPassword') do |m|
268
def POP3.delete_all( address, port = nil,
269
account = nil, password = nil,
270
isapop = false, &block )
271
start(address, port, account, password, isapop) {|pop|
272
pop.delete_all(&block)
276
# Opens a POP3 session, attempts authentication, and quits.
278
# This method raises POPAuthenticationError if authentication fails.
280
# === Example: normal POP3
282
# Net::POP3.auth_only('pop.example.com', 110,
283
# 'YourAccount', 'YourPassword')
287
# Net::POP3.auth_only('pop.example.com', 110,
288
# 'YourAccount', 'YourPassword', true)
290
def POP3.auth_only( address, port = nil,
291
account = nil, password = nil,
293
new(address, port, isapop).auth_only account, password
296
# Starts a pop3 session, attempts authentication, and quits.
297
# This method must not be called while POP3 session is opened.
298
# This method raises POPAuthenticationError if authentication fails.
299
def auth_only( account, password )
300
raise IOError, 'opening previously opened POP session' if started?
301
start(account, password) {
310
# Creates a new POP3 object and open the connection. Equivalent to
312
# Net::POP3.new(address, port, isapop).start(account, password)
314
# If +block+ is provided, yields the newly-opened POP3 object to it,
315
# and automatically closes it at the end of the session.
319
# Net::POP3.start(addr, port, account, password) do |pop|
320
# pop.each_mail do |m|
326
def POP3.start( address, port = nil,
327
account = nil, password = nil,
328
isapop = false, &block ) # :yield: pop
329
new(address, port, isapop).start(account, password, &block)
332
# Creates a new POP3 object.
334
# +address+ is the hostname or ip address of your POP3 server.
336
# The optional +port+ is the port to connect to; it defaults to 110.
338
# The optional +isapop+ specifies whether this connection is going
339
# to use APOP authentication; it defaults to +false+.
341
# This method does *not* open the TCP connection.
342
def initialize( addr, port = nil, isapop = false )
344
@port = port || self.class.default_port
359
# Does this instance use APOP authentication?
364
# Provide human-readable stringification of class state.
366
"#<#{self.class} #{@address}:#{@port} open=#{@started}>"
369
# *WARNING*: This method causes a serious security hole.
370
# Use this method only for debugging.
372
# Set an output stream for debugging.
376
# pop = Net::POP.new(addr, port)
377
# pop.set_debug_output $stderr
378
# pop.start(account, passwd) do |pop|
382
def set_debug_output( arg )
386
# The address to connect to.
389
# The port number to connect to.
392
# Seconds to wait until a connection is opened.
393
# If the POP3 object cannot open a connection within this time,
394
# it raises a TimeoutError exception.
395
attr_accessor :open_timeout
397
# Seconds to wait until reading one block (by one read(1) call).
398
# If the POP3 object cannot complete a read() within this time,
399
# it raises a TimeoutError exception.
400
attr_reader :read_timeout
402
# Set the read timeout.
403
def read_timeout=( sec )
404
@command.socket.read_timeout = sec if @command
408
# +true+ if the POP3 session has started.
413
alias active? started? #:nodoc: obsolete
415
# Starts a POP3 session.
417
# When called with block, gives a POP3 object to the block and
418
# closes the session after block call finishes.
420
# This method raises a POPAuthenticationError if authentication fails.
421
def start( account, password ) # :yield: pop
422
raise IOError, 'POP session already started' if @started
426
do_start account, password
432
do_start account, password
437
def do_start( account, password )
438
@socket = self.class.socket_type.old_open(@address, @port,
439
@open_timeout, @read_timeout, @debug_output)
441
@command = POP3Command.new(@socket)
443
@command.apop account, password
445
@command.auth account, password
449
do_finish if not @started
457
# Finishes a POP3 session and closes TCP connection.
459
raise IOError, 'POP session not yet started' unless started?
465
@command.quit if @command
469
@socket.close if @socket and not @socket.closed?
475
raise IOError, 'POP session not opened yet' \
476
if not @socket or @socket.closed?
482
# POP protocol wrapper
485
# Returns the number of messages on the POP server.
487
return @n_mails if @n_mails
488
@n_mails, @n_bytes = command().stat
492
# Returns the total size in bytes of all the messages on the POP server.
494
return @n_bytes if @n_bytes
495
@n_mails, @n_bytes = command().stat
499
# Returns an array of Net::POPMail objects, representing all the
500
# messages on the server. This array is renewed when the session
501
# restarts; otherwise, it is fetched from the server the first time
502
# this method is called (directly or indirectly) and cached.
504
# This method raises a POPError if an error occurs.
506
return @mails.dup if @mails
508
# some popd raises error for LIST on the empty mailbox.
513
@mails = command().list.map {|num, size|
514
POPMail.new(num, size, self, command())
519
# Yields each message to the passed-in block in turn.
522
# pop3.mails.each do |popmail|
526
# This method raises a POPError if an error occurs.
527
def each_mail( &block ) # :yield: message
533
# Deletes all messages on the server.
535
# If called with a block, yields each message in turn before deleting it.
540
# pop.delete_all do |m|
541
# File.open("inbox/#{n}") do |f|
547
# This method raises a POPError if an error occurs.
549
def delete_all # :yield: message
551
yield m if block_given?
552
m.delete unless m.deleted?
556
# Resets the session. This clears all "deleted" marks from messages.
558
# This method raises a POPError if an error occurs.
568
def set_all_uids #:nodoc: internal use only (called from POPMail#uidl)
569
command().uidl.each do |num, uid|
570
@mails.find {|m| m.number == num }.uid = uid
582
# This class is equivalent to POP3, except that it uses APOP authentication.
585
# Always returns true.
595
# This class represents a message which exists on the POP server.
596
# Instances of this class are created by the POP3 class; they should
597
# not be directly created by the user.
601
def initialize( num, len, pop, cmd ) #:nodoc:
610
# The sequence number of the message on the server.
613
# The length of the message in octets.
617
# Provide human-readable stringification of class state.
619
"#<#{self.class} #{@number}#{@deleted ? ' deleted' : ''}>"
623
# This method fetches the message. If called with a block, the
624
# message is yielded to the block one chunk at a time. If called
625
# without a block, the message is returned as a String. The optional
626
# +dest+ argument will be prepended to the returned String; this
627
# argument is essentially obsolete.
629
# === Example without block
631
# POP3.start('pop.example.com', 110,
632
# 'YourAccount, 'YourPassword') do |pop|
634
# pop.mails.each do |popmail|
635
# File.open("inbox/#{n}", 'w') do |f|
636
# f.write popmail.pop
643
# === Example with block
645
# POP3.start('pop.example.com', 110,
646
# 'YourAccount, 'YourPassword') do |pop|
648
# pop.mails.each do |popmail|
649
# File.open("inbox/#{n}", 'w') do |f|
650
# popmail.pop do |chunk| ####
658
# This method raises a POPError if an error occurs.
660
def pop( dest = '', &block ) # :yield: message_chunk
662
@command.retr(@number, &block)
665
@command.retr(@number) do |chunk|
672
alias all pop #:nodoc: obsolete
673
alias mail pop #:nodoc: obsolete
675
# Fetches the message header and +lines+ lines of body.
677
# The optional +dest+ argument is obsolete.
679
# This method raises a POPError if an error occurs.
680
def top( lines, dest = '' )
681
@command.top(@number, lines) do |chunk|
687
# Fetches the message header.
689
# The optional +dest+ argument is obsolete.
691
# This method raises a POPError if an error occurs.
692
def header( dest = '' )
696
# Marks a message for deletion on the server. Deletion does not
697
# actually occur until the end of the session; deletion may be
698
# cancelled for _all_ marked messages by calling POP3#reset().
700
# This method raises a POPError if an error occurs.
704
# POP3.start('pop.example.com', 110,
705
# 'YourAccount, 'YourPassword') do |pop|
707
# pop.mails.each do |popmail|
708
# File.open("inbox/#{n}", 'w') do |f|
709
# f.write popmail.pop
711
# popmail.delete ####
717
@command.dele @number
721
alias delete! delete #:nodoc: obsolete
723
# True if the mail has been deleted.
728
# Returns the unique-id of the message.
729
# Normally the unique-id is a hash string of the message.
731
# This method raises a POPError if an error occurs.
740
def uid=( uid ) #:nodoc: internal use only (used from POP3#set_all_uids)
747
class POP3Command #:nodoc: internal use only
749
def initialize( sock )
751
@error_occured = false
752
res = check_response(critical { recv_response() })
753
@apop_stamp = res.slice(/<.+>/)
757
"#<#{self.class} socket=#{@socket}>"
760
def auth( account, password )
761
check_response_auth(critical {
762
check_response_auth(get_response('USER %s', account))
763
get_response('PASS %s', password)
767
def apop( account, password )
768
raise POPAuthenticationError, 'not APOP server; cannot login' \
770
check_response_auth(critical {
771
get_response('APOP %s %s',
773
Digest::MD5.hexdigest(@apop_stamp + password))
781
@socket.each_list_item do |line|
782
m = /\A(\d+)[ \t]+(\d+)/.match(line) or
783
raise POPBadResponse, "bad response: #{line}"
784
list.push [m[1].to_i, m[2].to_i]
791
res = check_response(critical { get_response('STAT') })
792
m = /\A\+OK\s+(\d+)\s+(\d+)/.match(res) or
793
raise POPBadResponse, "wrong response format: #{res}"
794
[m[1].to_i, m[2].to_i]
798
check_response(critical { get_response 'RSET' })
801
def top( num, lines = 0, &block )
803
getok('TOP %d %d', num, lines)
804
@socket.each_message_chunk(&block)
808
def retr( num, &block )
810
getok('RETR %d', num)
811
@socket.each_message_chunk(&block)
816
check_response(critical { get_response('DELE %d', num) })
819
def uidl( num = nil )
821
res = check_response(critical { get_response('UIDL %d', num) })
822
return res.split(/ /)[1]
827
@socket.each_list_item do |line|
828
num, uid = line.split
829
table[num.to_i] = uid
837
check_response(critical { get_response('QUIT') })
842
def getok( fmt, *fargs )
843
@socket.writeline sprintf(fmt, *fargs)
844
check_response(recv_response())
847
def get_response( fmt, *fargs )
848
@socket.writeline sprintf(fmt, *fargs)
856
def check_response( res )
857
raise POPError, res unless /\A\+OK/i === res
861
def check_response_auth( res )
862
raise POPAuthenticationError, res unless /\A\+OK/i === res
867
return '+OK dummy ok response' if @error_occured
871
@error_occured = true
876
end # class POP3Command