DEFINITIONS
This source file includes following functions.
1 =begin
2
3 = net/pop.rb
4
5 Copyright (c) 1999-2002 Yukihiro Matsumoto
6
7 written & maintained by Minero Aoki <aamine@loveruby.net>
8
9 This program is free software. You can re-distribute and/or
10 modify this program under the same terms as Ruby itself,
11 Ruby Distribute License or GNU General Public License.
12
13 NOTE: You can find Japanese version of this document in
14 the doc/net directory of the standard ruby interpreter package.
15
16 $Id: pop.rb,v 1.47 2002/03/26 11:18:02 aamine Exp $
17
18 == What is This Module?
19
20 This module provides your program the functions to retrieve
21 mails via POP3, Post Office Protocol version 3. For details
22 of POP3, refer [RFC1939] ((<URL:http:
23
24 == Examples
25
26 === Retrieving Mails
27
28 This example retrieves mails from server and delete it (on server).
29 Mails are written in file named 'inbox/1', 'inbox/2', ....
30 Replace 'pop3.server.address' your POP3 server address.
31
32 require 'net/pop'
33
34 pop = Net::POP3.new( 'pop3.server.address', 110 )
35 pop.start( 'YourAccount', 'YourPassword' ) ###
36 if pop.mails.empty? then
37 puts 'no mail.'
38 else
39 i = 0
40 pop.each_mail do |m| # or "pop.mails.each ..."
41 File.open( 'inbox/' + i.to_s, 'w' ) {|f|
42 f.write m.pop
43 }
44 m.delete
45 i += 1
46 end
47 puts "#{pop.mails.size} mails popped."
48 end
49 pop.finish ###
50
51 (1) call Net::POP3#start and start POP session
52 (2) access mails by using POP3#each_mail and/or POP3#mails
53 (3) close POP session by calling POP3#finish or use block form #start.
54
55 This example is using block form #start to close the session.
56 === Enshort Code
57
58 The example above is very verbose. You can enshort code by using
59 some utility methods. At first, block form of Net::POP3.start can
60 alternates POP3.new, POP3#start and POP3#finish.
61
62 require 'net/pop'
63
64 Net::POP3.start( 'pop3.server.address', 110 )
65 'YourAccount', 'YourPassword' )
66 if pop.mails.empty? then
67 puts 'no mail.'
68 else
69 i = 0
70 pop.each_mail do |m| # or "pop.mails.each ..."
71 File.open( 'inbox/' + i.to_s, 'w' ) {|f|
72 f.write m.pop
73 }
74 m.delete
75 i += 1
76 end
77 puts "#{pop.mails.size} mails popped."
78 end
79 }
80
81 POP3#delete_all alternates #each_mail and m.delete.
82
83 require 'net/pop'
84
85 Net::POP3.start( 'pop3.server.address', 110,
86 'YourAccount', 'YourPassword' ) {|pop|
87 if pop.mails.empty? then
88 puts 'no mail.'
89 else
90 i = 0
91 pop.delete_all do |m|
92 File.open( 'inbox/' + i.to_s, 'w' ) {|f|
93 f.write m.pop
94 }
95 i += 1
96 end
97 end
98 }
99
100 And here is more shorter example.
101
102 require 'net/pop'
103
104 i = 0
105 Net::POP3.delete_all( 'pop3.server.address', 110,
106 'YourAccount', 'YourPassword' ) do |m|
107 File.open( 'inbox/' + i.to_s, 'w' ) {|f|
108 f.write m.pop
109 }
110 i += 1
111 end
112
113 === Writing to File directly
114
115 All examples above get mail as one big string.
116 This example does not create such one.
117
118 require 'net/pop'
119 Net::POP3.delete_all( 'pop3.server.address', 110,
120 'YourAccount', 'YourPassword' ) do |m|
121 File.open( 'inbox', 'w' ) {|f|
122 m.pop f ####
123 }
124 end
125
126 === Using APOP
127
128 The net/pop library supports APOP authentication.
129 To use APOP, use Net::APOP class instead of Net::POP3 class.
130 You can use utility method, Net::POP3.APOP(). Example:
131
132 require 'net/pop'
133
134 # use APOP authentication if $isapop == true
135 pop = Net::POP3.APOP($isapop).new( 'apop.server.address', 110 )
136 pop.start( YourAccount', 'YourPassword' ) {|pop|
137 # Rest code is same.
138 }
139
140
141 == Net::POP3 class
142
143 === Class Methods
144
145 : new( address, port = 110, apop = false )
146 creates a new Net::POP3 object.
147 This method does not open TCP connection yet.
148
149 : start( address, port = 110, account, password )
150 : start( address, port = 110, account, password ) {|pop| .... }
151 equals to Net::POP3.new( address, port ).start( account, password )
152
153 Net::POP3.start( addr, port, account, password ) {|pop|
154 pop.each_mail do |m|
155 file.write m.pop
156 m.delete
157 end
158 }
159
160 : APOP( is_apop )
161 returns Net::APOP class object if IS_APOP is true.
162 returns Net::POP3 class object if false.
163 Use this method like:
164
165 # example 1
166 pop = Net::POP3::APOP($isapop).new( addr, port )
167
168 # example 2
169 Net::POP3::APOP($isapop).start( addr, port ) {|pop|
170 ....
171 }
172
173 : foreach( address, port = 110, account, password ) {|mail| .... }
174 starts POP3 protocol and iterates for each POPMail object.
175 This method equals to
176
177 Net::POP3.start( address, port, account, password ) {|pop|
178 pop.each_mail do |m|
179 yield m
180 end
181 }
182
183 # example
184 Net::POP3.foreach( 'your.pop.server', 110,
185 'YourAccount', 'YourPassword' ) do |m|
186 file.write m.pop
187 m.delete if $DELETE
188 end
189
190 : delete_all( address, port = 110, account, password )
191 : delete_all( address, port = 110, account, password ) {|mail| .... }
192 starts POP3 session and delete all mails.
193 If block is given, iterates for each POPMail object before delete.
194
195 # example
196 Net::POP3.delete_all( addr, nil, 'YourAccount', 'YourPassword' ) do |m|
197 m.pop file
198 end
199
200 : auth_only( address, port = 110, account, password )
201 (just for POP-before-SMTP)
202 opens POP3 session and does autholize and quit.
203 This method must not be called while POP3 session is opened.
204
205 # example
206 Net::POP3.auth_only( 'your.pop3.server',
207 nil, # using default (110)
208 'YourAccount',
209 'YourPassword' )
210
211 === Instance Methods
212
213 : start( account, password )
214 : start( account, password ) {|pop| .... }
215 starts POP3 session.
216
217 When called with block, gives a POP3 object to block and
218 closes the session after block call finish.
219
220 : active?
221 true if POP3 session is started.
222
223 : address
224 the address to connect
225
226 : port
227 the port number to connect
228
229 : open_timeout
230 : open_timeout=(n)
231 seconds to wait until connection is opened.
232 If POP3 object cannot open a conection in this seconds,
233 it raises TimeoutError exception.
234
235 : read_timeout
236 : read_timeout=(n)
237 seconds to wait until reading one block (by one read(1) call).
238 If POP3 object cannot open a conection in this seconds,
239 it raises TimeoutError exception.
240
241 : finish
242 finishes POP3 session.
243 If POP3 session had not be started, raises an IOError.
244
245 : mails
246 an array of Net::POPMail objects.
247 This array is renewed when session started.
248
249 : each_mail {|popmail| .... }
250 : each {|popmail| .... }
251 is equals to "pop3.mails.each"
252
253 : delete_all
254 : delete_all {|popmail| .... }
255 deletes all mails on server.
256 If called with block, gives mails to the block before deleting.
257
258 # example
259 n = 1
260 pop.delete_all do |m|
261 File.open("inbox/#{n}") {|f| f.write m.pop }
262 n += 1
263 end
264
265 : auth_only( account, password )
266 (just for POP-before-SMTP)
267 opens POP3 session and does autholize and quit.
268 This method must not be called while POP3 session is opened.
269 # example
270 pop = Net::POP3.new( 'your.pop3.server' )
271 pop.auth_only 'YourAccount', 'YourPassword'
272
273 : reset
274 reset the session. All "deleted mark" are removed.
275
276 == Net::APOP
277
278 This class defines no new methods.
279 Only difference from POP3 is using APOP authentification.
280
281 === Super Class
282 Net::POP3
283
284 == Net::POPMail
285
286 A class of mail which exists on POP server.
287
288 === Instance Methods
289
290 : pop( dest = '' )
291 This method fetches a mail and write to 'dest' using '<<' method.
292
293 # example
294 allmails = nil
295 POP3.start( 'your.pop3.server', 110,
296 'YourAccount, 'YourPassword' ) {|pop|
297 allmails = pop.mails.collect {|popmail| popmail.pop }
298 }
299
300 : pop {|str| .... }
301 gives the block part strings of a mail.
302
303 # example
304 POP3.start( 'localhost', 110 ) {|pop3|
305 pop3.each_mail do |m|
306 m.pop do |str|
307 # do anything
308 end
309 end
310 }
311
312 : header
313 This method fetches only mail header.
314
315 : top( lines )
316 This method fetches mail header and LINES lines of body.
317
318 : delete
319 deletes mail on server.
320
321 : size
322 mail size (bytes)
323
324 : deleted?
325 true if mail was deleted
326
327 =end
328
329 require 'net/protocol'
330 require 'digest/md5'
331
332
333 module Net
334
335 class POP3 < Protocol
336
337 protocol_param :default_port, '110'
338 protocol_param :command_type, '::Net::POP3Command'
339 protocol_param :apop_command_type, '::Net::APOPCommand'
340 protocol_param :mail_type, '::Net::POPMail'
341 protocol_param :socket_type, '::Net::InternetMessageIO'
342
343
344 def POP3.APOP( bool )
345 bool ? APOP : POP3
346 end
347
348 def POP3.foreach( address, port = nil,
349 account = nil, password = nil, &block )
350 start( address, port, account, password ) {|pop|
351 pop.each_mail( &block )
352 }
353 end
354
355 def POP3.delete_all( address, port = nil,
356 account = nil, password = nil, &block )
357 start( address, port, account, password ) {|pop|
358 pop.delete_all( &block )
359 }
360 end
361
362 def POP3.auth_only( address, port = nil,
363 account = nil, password = nil )
364 new( address, port ).auth_only account, password
365 end
366
367
368 def auth_only( account, password )
369 raise IOError, 'opening already opened POP session' if active?
370 start( account, password ) {
371 # none
372 }
373 end
374
375
376 #
377 # connection
378 #
379
380 def initialize( addr, port = nil, apop = false )
381 super addr, port
382 @mails = nil
383 @apop = false
384 end
385
386 private
387
388 def do_start( account, password )
389 conn_socket
390 @command = (@apop ? type.apop_command_type : type.command_type).new(socket())
391 @command.auth account, password
392 end
393
394 def do_finish
395 @mails = nil
396 disconn_command
397 disconn_socket
398 end
399
400
401 #
402 # POP operations
403 #
404
405 public
406
407 def mails
408 return @mails if @mails
409
410 mails = []
411 mtype = type.mail_type
412 command().list.each_with_index do |size,idx|
413 mails.push mtype.new(idx, size, command()) if size
414 end
415 @mails = mails.freeze
416 end
417
418 def each_mail( &block )
419 mails().each( &block )
420 end
421
422 alias each each_mail
423
424 def delete_all
425 mails().each do |m|
426 yield m if block_given?
427 m.delete unless m.deleted?
428 end
429 end
430
431 def reset
432 command().rset
433 mails().each do |m|
434 m.instance_eval { @deleted = false }
435 end
436 end
437
438
439 def command
440 io_check
441 super
442 end
443
444 def io_check
445 (not socket() or socket().closed?) and
446 raise IOError, 'POP session is not opened yet'
447 end
448
449 end
450
451 POP = POP3
452 POPSession = POP3
453 POP3Session = POP3
454
455
456 class APOP < POP3
457 def APOP.command_type
458 APOPCommand
459 end
460 end
461
462 APOPSession = APOP
463
464
465 class POPMail
466
467 def initialize( n, s, cmd )
468 @num = n
469 @size = s
470 @command = cmd
471
472 @deleted = false
473 end
474
475 attr :size
476
477 def inspect
478 "#<#{type} #{@num}#{@deleted ? ' deleted' : ''}>"
479 end
480
481 def pop( dest = '', &block )
482 if block then
483 dest = ReadAdapter.new(block)
484 end
485 @command.retr @num, dest
486 end
487
488 alias all pop
489 alias mail pop
490
491 def top( lines, dest = '' )
492 @command.top @num, lines, dest
493 end
494
495 def header( dest = '' )
496 top 0, dest
497 end
498
499 def delete
500 @command.dele @num
501 @deleted = true
502 end
503
504 alias delete! delete
505
506 def deleted?
507 @deleted
508 end
509
510 def uidl
511 @command.uidl @num
512 end
513
514 end
515
516
517 class POP3Command < Command
518
519 def initialize( sock )
520 super
521 atomic {
522 check_reply SuccessCode
523 }
524 end
525
526 def auth( account, pass )
527 atomic {
528 @socket.writeline 'USER ' + account
529 check_reply_auth
530
531 @socket.writeline 'PASS ' + pass
532 check_reply_auth
533 }
534 end
535
536 def list
537 arr = []
538 atomic {
539 getok 'LIST'
540 @socket.each_list_item do |line|
541 m = /\A(\d+)[ \t]+(\d+)/.match(line) or
542 raise BadResponse, "illegal response: #{line}"
543 arr[ m[1].to_i ] = m[2].to_i
544 end
545 }
546 arr
547 end
548
549 def rset
550 atomic {
551 getok 'RSET'
552 }
553 end
554
555
556 def top( num, lines = 0, dest = '' )
557 atomic {
558 getok sprintf('TOP %d %d', num, lines)
559 @socket.read_message_to dest
560 }
561 end
562
563 def retr( num, dest = '' )
564 atomic {
565 getok sprintf('RETR %d', num)
566 @socket.read_message_to dest
567 }
568 end
569
570 def dele( num )
571 atomic {
572 getok sprintf('DELE %d', num)
573 }
574 end
575
576 def uidl( num )
577 atomic {
578 getok( sprintf('UIDL %d', num) ).message.split(' ')[1]
579 }
580 end
581
582 def quit
583 atomic {
584 getok 'QUIT'
585 }
586 end
587
588 private
589
590 def check_reply_auth
591 begin
592 return check_reply(SuccessCode)
593 rescue ProtocolError => err
594 raise ProtoAuthError.new('Fail to POP authentication', err.response)
595 end
596 end
597
598 def get_reply
599 str = @socket.readline
600
601 if /\A\+/ === str then
602 Response.new( SuccessCode, str[0,3], str[3, str.size - 3].strip )
603 else
604 Response.new( ErrorCode, str[0,4], str[4, str.size - 4].strip )
605 end
606 end
607
608 end
609
610
611 class APOPCommand < POP3Command
612
613 def initialize( sock )
614 response = super(sock)
615 m = /<.+>/.match(response.msg) or
616 raise ProtoAuthError.new("not APOP server: cannot login", nil)
617 @stamp = m[0]
618 end
619
620 def auth( account, pass )
621 atomic {
622 @socket.writeline sprintf('APOP %s %s',
623 account,
624 Digest::MD5.hexdigest(@stamp + pass))
625 check_reply_auth
626 }
627 end
628
629 end
630
631 end # module Net