lib/net/pop.rb


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://www.ietf.org/rfc/rfc1939.txt>)).
  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