~ubuntu-branches/ubuntu/hardy/ruby1.8/hardy-updates

« back to all changes in this revision

Viewing changes to lib/net/pop.rb

  • Committer: Bazaar Package Importer
  • Author(s): akira yamada
  • Date: 2007-03-13 22:11:58 UTC
  • mfrom: (1.1.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20070313221158-h3oql37brlaf2go2
Tags: 1.8.6-1
* new upstream version, 1.8.6.
* libruby1.8 conflicts with libopenssl-ruby1.8 (< 1.8.6) (closes: #410018)
* changed packaging style to cdbs from dbs.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 <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 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
 
 
23
require 'net/protocol'
 
24
require 'digest/md5'
 
25
 
 
26
module 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
 
 
878
end   # module Net
 
879