source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/net/pop.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 22.9 KB
Line 
1# = net/pop.rb
2#
3# Copyright (c) 1999-2003 Yukihiro Matsumoto.
4#
5# Copyright (c) 1999-2003 Minero Aoki.
6#
7# Written & maintained by Minero Aoki <[email protected]>.
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 or GNU General Public License.
14#
15# NOTE: You can find Japanese version of this document in
16# the doc/net directory of the standard ruby interpreter package.
17#
18# $Id: pop.rb 11708 2007-02-12 23:01:19Z shyouhei $
19#
20# See Net::POP3 for documentation.
21#
22
23require 'net/protocol'
24require 'digest/md5'
25
26module Net
27
28 # Non-authentication POP3 protocol error
29 # (reply code "-ERR", except authentication).
30 class POPError < ProtocolError; end
31
32 # POP3 authentication error.
33 class POPAuthenticationError < ProtoAuthError; end
34
35 # Unexpected response from the server.
36 class POPBadResponse < POPError; end
37
38 #
39 # = Net::POP3
40 #
41 # == What is This Library?
42 #
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).
46 #
47 # == Examples
48 #
49 # === Retrieving Messages
50 #
51 # This example retrieves messages from the server and deletes them
52 # on the server.
53 #
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
57 # details.
58 #
59 # require 'net/pop'
60 #
61 # pop = Net::POP3.new('pop.example.com')
62 # pop.start('YourAccount', 'YourPassword') # (1)
63 # if pop.mails.empty?
64 # puts 'No mail.'
65 # else
66 # i = 0
67 # pop.each_mail do |m| # or "pop.mails.each ..." # (2)
68 # File.open("inbox/#{i}", 'w') do |f|
69 # f.write m.pop
70 # end
71 # m.delete
72 # i += 1
73 # end
74 # puts "#{pop.mails.size} mails popped."
75 # end
76 # pop.finish # (3)
77 #
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.
81 #
82 # === Shortened Code
83 #
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.
87 #
88 # require 'net/pop'
89 #
90 # Net::POP3.start('pop.example.com', 110,
91 # 'YourAccount', 'YourPassword') do |pop|
92 # if pop.mails.empty?
93 # puts 'No mail.'
94 # else
95 # i = 0
96 # pop.each_mail do |m| # or "pop.mails.each ..."
97 # File.open("inbox/#{i}", 'w') do |f|
98 # f.write m.pop
99 # end
100 # m.delete
101 # i += 1
102 # end
103 # puts "#{pop.mails.size} mails popped."
104 # end
105 # end
106 #
107 # POP3#delete_all is an alternative for #each_mail and #delete.
108 #
109 # require 'net/pop'
110 #
111 # Net::POP3.start('pop.example.com', 110,
112 # 'YourAccount', 'YourPassword') do |pop|
113 # if pop.mails.empty?
114 # puts 'No mail.'
115 # else
116 # i = 1
117 # pop.delete_all do |m|
118 # File.open("inbox/#{i}", 'w') do |f|
119 # f.write m.pop
120 # end
121 # i += 1
122 # end
123 # end
124 # end
125 #
126 # And here is an even shorter example.
127 #
128 # require 'net/pop'
129 #
130 # i = 0
131 # Net::POP3.delete_all('pop.example.com', 110,
132 # 'YourAccount', 'YourPassword') do |m|
133 # File.open("inbox/#{i}", 'w') do |f|
134 # f.write m.pop
135 # end
136 # i += 1
137 # end
138 #
139 # === Memory Space Issues
140 #
141 # All the examples above get each message as one big string.
142 # This example avoids this.
143 #
144 # require 'net/pop'
145 #
146 # i = 1
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.
151 # f.write chunk
152 # end
153 # i += 1
154 # end
155 # end
156 #
157 # === Using APOP
158 #
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:
162 #
163 # require 'net/pop'
164 #
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.
169 # end
170 #
171 # === Fetch Only Selected Mail Using 'UIDL' POP Command
172 #
173 # If your POP server provides UIDL functionality,
174 # you can grab only selected mails from the POP server.
175 # e.g.
176 #
177 # def need_pop?( id )
178 # # determine if we need pop this mail...
179 # end
180 #
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)
185 # end
186 # end
187 #
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.
190 #
191 class POP3 < Protocol
192
193 Revision = %q$Revision: 11708 $.split[1]
194
195 #
196 # Class Parameters
197 #
198
199 # The default port for POP3 connections, port 110
200 def POP3.default_port
201 110
202 end
203
204 def POP3.socket_type #:nodoc: obsolete
205 Net::InternetMessageIO
206 end
207
208 #
209 # Utilities
210 #
211
212 # Returns the APOP class if +isapop+ is true; otherwise, returns
213 # the POP class. For example:
214 #
215 # # Example 1
216 # pop = Net::POP3::APOP($is_apop).new(addr, port)
217 #
218 # # Example 2
219 # Net::POP3::APOP($is_apop).start(addr, port) do |pop|
220 # ....
221 # end
222 #
223 def POP3.APOP( isapop )
224 isapop ? APOP : POP3
225 end
226
227 # Starts a POP3 session and iterates over each POPMail object,
228 # yielding it to the +block+.
229 # This method is equivalent to:
230 #
231 # Net::POP3.start(address, port, account, password) do |pop|
232 # pop.each_mail do |m|
233 # yield m
234 # end
235 # end
236 #
237 # This method raises a POPAuthenticationError if authentication fails.
238 #
239 # === Example
240 #
241 # Net::POP3.foreach('pop.example.com', 110,
242 # 'YourAccount', 'YourPassword') do |m|
243 # file.write m.pop
244 # m.delete if $DELETE
245 # end
246 #
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)
252 }
253 end
254
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
257 # being deleted.
258 #
259 # This method raises a POPAuthenticationError if authentication fails.
260 #
261 # === Example
262 #
263 # Net::POP3.delete_all('pop.example.com', 110,
264 # 'YourAccount', 'YourPassword') do |m|
265 # file.write m.pop
266 # end
267 #
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)
273 }
274 end
275
276 # Opens a POP3 session, attempts authentication, and quits.
277 #
278 # This method raises POPAuthenticationError if authentication fails.
279 #
280 # === Example: normal POP3
281 #
282 # Net::POP3.auth_only('pop.example.com', 110,
283 # 'YourAccount', 'YourPassword')
284 #
285 # === Example: APOP
286 #
287 # Net::POP3.auth_only('pop.example.com', 110,
288 # 'YourAccount', 'YourPassword', true)
289 #
290 def POP3.auth_only( address, port = nil,
291 account = nil, password = nil,
292 isapop = false )
293 new(address, port, isapop).auth_only account, password
294 end
295
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) {
302 ;
303 }
304 end
305
306 #
307 # Session management
308 #
309
310 # Creates a new POP3 object and open the connection. Equivalent to
311 #
312 # Net::POP3.new(address, port, isapop).start(account, password)
313 #
314 # If +block+ is provided, yields the newly-opened POP3 object to it,
315 # and automatically closes it at the end of the session.
316 #
317 # === Example
318 #
319 # Net::POP3.start(addr, port, account, password) do |pop|
320 # pop.each_mail do |m|
321 # file.write m.pop
322 # m.delete
323 # end
324 # end
325 #
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)
330 end
331
332 # Creates a new POP3 object.
333 #
334 # +address+ is the hostname or ip address of your POP3 server.
335 #
336 # The optional +port+ is the port to connect to; it defaults to 110.
337 #
338 # The optional +isapop+ specifies whether this connection is going
339 # to use APOP authentication; it defaults to +false+.
340 #
341 # This method does *not* open the TCP connection.
342 def initialize( addr, port = nil, isapop = false )
343 @address = addr
344 @port = port || self.class.default_port
345 @apop = isapop
346
347 @command = nil
348 @socket = nil
349 @started = false
350 @open_timeout = 30
351 @read_timeout = 60
352 @debug_output = nil
353
354 @mails = nil
355 @n_mails = nil
356 @n_bytes = nil
357 end
358
359 # Does this instance use APOP authentication?
360 def apop?
361 @apop
362 end
363
364 # Provide human-readable stringification of class state.
365 def inspect
366 "#<#{self.class} #{@address}:#{@port} open=#{@started}>"
367 end
368
369 # *WARNING*: This method causes a serious security hole.
370 # Use this method only for debugging.
371 #
372 # Set an output stream for debugging.
373 #
374 # === Example
375 #
376 # pop = Net::POP.new(addr, port)
377 # pop.set_debug_output $stderr
378 # pop.start(account, passwd) do |pop|
379 # ....
380 # end
381 #
382 def set_debug_output( arg )
383 @debug_output = arg
384 end
385
386 # The address to connect to.
387 attr_reader :address
388
389 # The port number to connect to.
390 attr_reader :port
391
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
396
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
401
402 # Set the read timeout.
403 def read_timeout=( sec )
404 @command.socket.read_timeout = sec if @command
405 @read_timeout = sec
406 end
407
408 # +true+ if the POP3 session has started.
409 def started?
410 @started
411 end
412
413 alias active? started? #:nodoc: obsolete
414
415 # Starts a POP3 session.
416 #
417 # When called with block, gives a POP3 object to the block and
418 # closes the session after block call finishes.
419 #
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
423
424 if block_given?
425 begin
426 do_start account, password
427 return yield(self)
428 ensure
429 do_finish
430 end
431 else
432 do_start account, password
433 return self
434 end
435 end
436
437 def do_start( account, password )
438 @socket = self.class.socket_type.old_open(@address, @port,
439 @open_timeout, @read_timeout, @debug_output)
440 on_connect
441 @command = POP3Command.new(@socket)
442 if apop?
443 @command.apop account, password
444 else
445 @command.auth account, password
446 end
447 @started = true
448 ensure
449 do_finish if not @started
450 end
451 private :do_start
452
453 def on_connect
454 end
455 private :on_connect
456
457 # Finishes a POP3 session and closes TCP connection.
458 def finish
459 raise IOError, 'POP session not yet started' unless started?
460 do_finish
461 end
462
463 def do_finish
464 @mails = nil
465 @command.quit if @command
466 ensure
467 @started = false
468 @command = nil
469 @socket.close if @socket and not @socket.closed?
470 @socket = nil
471 end
472 private :do_finish
473
474 def command
475 raise IOError, 'POP session not opened yet' \
476 if not @socket or @socket.closed?
477 @command
478 end
479 private :command
480
481 #
482 # POP protocol wrapper
483 #
484
485 # Returns the number of messages on the POP server.
486 def n_mails
487 return @n_mails if @n_mails
488 @n_mails, @n_bytes = command().stat
489 @n_mails
490 end
491
492 # Returns the total size in bytes of all the messages on the POP server.
493 def n_bytes
494 return @n_bytes if @n_bytes
495 @n_mails, @n_bytes = command().stat
496 @n_bytes
497 end
498
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.
503 #
504 # This method raises a POPError if an error occurs.
505 def mails
506 return @mails.dup if @mails
507 if n_mails() == 0
508 # some popd raises error for LIST on the empty mailbox.
509 @mails = []
510 return []
511 end
512
513 @mails = command().list.map {|num, size|
514 POPMail.new(num, size, self, command())
515 }
516 @mails.dup
517 end
518
519 # Yields each message to the passed-in block in turn.
520 # Equivalent to:
521 #
522 # pop3.mails.each do |popmail|
523 # ....
524 # end
525 #
526 # This method raises a POPError if an error occurs.
527 def each_mail( &block ) # :yield: message
528 mails().each(&block)
529 end
530
531 alias each each_mail
532
533 # Deletes all messages on the server.
534 #
535 # If called with a block, yields each message in turn before deleting it.
536 #
537 # === Example
538 #
539 # n = 1
540 # pop.delete_all do |m|
541 # File.open("inbox/#{n}") do |f|
542 # f.write m.pop
543 # end
544 # n += 1
545 # end
546 #
547 # This method raises a POPError if an error occurs.
548 #
549 def delete_all # :yield: message
550 mails().each do |m|
551 yield m if block_given?
552 m.delete unless m.deleted?
553 end
554 end
555
556 # Resets the session. This clears all "deleted" marks from messages.
557 #
558 # This method raises a POPError if an error occurs.
559 def reset
560 command().rset
561 mails().each do |m|
562 m.instance_eval {
563 @deleted = false
564 }
565 end
566 end
567
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
571 end
572 end
573
574 end # class POP3
575
576 # class aliases
577 POP = POP3
578 POPSession = POP3
579 POP3Session = POP3
580
581 #
582 # This class is equivalent to POP3, except that it uses APOP authentication.
583 #
584 class APOP < POP3
585 # Always returns true.
586 def apop?
587 true
588 end
589 end
590
591 # class aliases
592 APOPSession = APOP
593
594 #
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.
598 #
599 class POPMail
600
601 def initialize( num, len, pop, cmd ) #:nodoc:
602 @number = num
603 @length = len
604 @pop = pop
605 @command = cmd
606 @deleted = false
607 @uid = nil
608 end
609
610 # The sequence number of the message on the server.
611 attr_reader :number
612
613 # The length of the message in octets.
614 attr_reader :length
615 alias size length
616
617 # Provide human-readable stringification of class state.
618 def inspect
619 "#<#{self.class} #{@number}#{@deleted ? ' deleted' : ''}>"
620 end
621
622 #
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.
628 #
629 # === Example without block
630 #
631 # POP3.start('pop.example.com', 110,
632 # 'YourAccount, 'YourPassword') do |pop|
633 # n = 1
634 # pop.mails.each do |popmail|
635 # File.open("inbox/#{n}", 'w') do |f|
636 # f.write popmail.pop
637 # end
638 # popmail.delete
639 # n += 1
640 # end
641 # end
642 #
643 # === Example with block
644 #
645 # POP3.start('pop.example.com', 110,
646 # 'YourAccount, 'YourPassword') do |pop|
647 # n = 1
648 # pop.mails.each do |popmail|
649 # File.open("inbox/#{n}", 'w') do |f|
650 # popmail.pop do |chunk| ####
651 # f.write chunk
652 # end
653 # end
654 # n += 1
655 # end
656 # end
657 #
658 # This method raises a POPError if an error occurs.
659 #
660 def pop( dest = '', &block ) # :yield: message_chunk
661 if block_given?
662 @command.retr(@number, &block)
663 nil
664 else
665 @command.retr(@number) do |chunk|
666 dest << chunk
667 end
668 dest
669 end
670 end
671
672 alias all pop #:nodoc: obsolete
673 alias mail pop #:nodoc: obsolete
674
675 # Fetches the message header and +lines+ lines of body.
676 #
677 # The optional +dest+ argument is obsolete.
678 #
679 # This method raises a POPError if an error occurs.
680 def top( lines, dest = '' )
681 @command.top(@number, lines) do |chunk|
682 dest << chunk
683 end
684 dest
685 end
686
687 # Fetches the message header.
688 #
689 # The optional +dest+ argument is obsolete.
690 #
691 # This method raises a POPError if an error occurs.
692 def header( dest = '' )
693 top(0, dest)
694 end
695
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().
699 #
700 # This method raises a POPError if an error occurs.
701 #
702 # === Example
703 #
704 # POP3.start('pop.example.com', 110,
705 # 'YourAccount, 'YourPassword') do |pop|
706 # n = 1
707 # pop.mails.each do |popmail|
708 # File.open("inbox/#{n}", 'w') do |f|
709 # f.write popmail.pop
710 # end
711 # popmail.delete ####
712 # n += 1
713 # end
714 # end
715 #
716 def delete
717 @command.dele @number
718 @deleted = true
719 end
720
721 alias delete! delete #:nodoc: obsolete
722
723 # True if the mail has been deleted.
724 def deleted?
725 @deleted
726 end
727
728 # Returns the unique-id of the message.
729 # Normally the unique-id is a hash string of the message.
730 #
731 # This method raises a POPError if an error occurs.
732 def unique_id
733 return @uid if @uid
734 @pop.set_all_uids
735 @uid
736 end
737
738 alias uidl unique_id
739
740 def uid=( uid ) #:nodoc: internal use only (used from POP3#set_all_uids)
741 @uid = uid
742 end
743
744 end # class POPMail
745
746
747 class POP3Command #:nodoc: internal use only
748
749 def initialize( sock )
750 @socket = sock
751 @error_occured = false
752 res = check_response(critical { recv_response() })
753 @apop_stamp = res.slice(/<.+>/)
754 end
755
756 def inspect
757 "#<#{self.class} socket=#{@socket}>"
758 end
759
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)
764 })
765 end
766
767 def apop( account, password )
768 raise POPAuthenticationError, 'not APOP server; cannot login' \
769 unless @apop_stamp
770 check_response_auth(critical {
771 get_response('APOP %s %s',
772 account,
773 Digest::MD5.hexdigest(@apop_stamp + password))
774 })
775 end
776
777 def list
778 critical {
779 getok 'LIST'
780 list = []
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]
785 end
786 return list
787 }
788 end
789
790 def stat
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]
795 end
796
797 def rset
798 check_response(critical { get_response 'RSET' })
799 end
800
801 def top( num, lines = 0, &block )
802 critical {
803 getok('TOP %d %d', num, lines)
804 @socket.each_message_chunk(&block)
805 }
806 end
807
808 def retr( num, &block )
809 critical {
810 getok('RETR %d', num)
811 @socket.each_message_chunk(&block)
812 }
813 end
814
815 def dele( num )
816 check_response(critical { get_response('DELE %d', num) })
817 end
818
819 def uidl( num = nil )
820 if num
821 res = check_response(critical { get_response('UIDL %d', num) })
822 return res.split(/ /)[1]
823 else
824 critical {
825 getok('UIDL')
826 table = {}
827 @socket.each_list_item do |line|
828 num, uid = line.split
829 table[num.to_i] = uid
830 end
831 return table
832 }
833 end
834 end
835
836 def quit
837 check_response(critical { get_response('QUIT') })
838 end
839
840 private
841
842 def getok( fmt, *fargs )
843 @socket.writeline sprintf(fmt, *fargs)
844 check_response(recv_response())
845 end
846
847 def get_response( fmt, *fargs )
848 @socket.writeline sprintf(fmt, *fargs)
849 recv_response()
850 end
851
852 def recv_response
853 @socket.readline
854 end
855
856 def check_response( res )
857 raise POPError, res unless /\A\+OK/i === res
858 res
859 end
860
861 def check_response_auth( res )
862 raise POPAuthenticationError, res unless /\A\+OK/i === res
863 res
864 end
865
866 def critical
867 return '+OK dummy ok response' if @error_occured
868 begin
869 return yield()
870 rescue Exception
871 @error_occured = true
872 raise
873 end
874 end
875
876 end # class POP3Command
877
878end # module Net
879
Note: See TracBrowser for help on using the repository browser.