(root)/
Python-3.11.7/
Lib/
poplib.py
       1  """A POP3 client class.
       2  
       3  Based on the J. Myers POP3 draft, Jan. 96
       4  """
       5  
       6  # Author: David Ascher <david_ascher@brown.edu>
       7  #         [heavily stealing from nntplib.py]
       8  # Updated: Piers Lauder <piers@cs.su.oz.au> [Jul '97]
       9  # String method conversion and test jig improvements by ESR, February 2001.
      10  # Added the POP3_SSL class. Methods loosely based on IMAP_SSL. Hector Urtubia <urtubia@mrbook.org> Aug 2003
      11  
      12  # Example (see the test function at the end of this file)
      13  
      14  # Imports
      15  
      16  import errno
      17  import re
      18  import socket
      19  import sys
      20  
      21  try:
      22      import ssl
      23      HAVE_SSL = True
      24  except ImportError:
      25      HAVE_SSL = False
      26  
      27  __all__ = ["POP3","error_proto"]
      28  
      29  # Exception raised when an error or invalid response is received:
      30  
      31  class ESC[4;38;5;81merror_proto(ESC[4;38;5;149mException): pass
      32  
      33  # Standard Port
      34  POP3_PORT = 110
      35  
      36  # POP SSL PORT
      37  POP3_SSL_PORT = 995
      38  
      39  # Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
      40  CR = b'\r'
      41  LF = b'\n'
      42  CRLF = CR+LF
      43  
      44  # maximal line length when calling readline(). This is to prevent
      45  # reading arbitrary length lines. RFC 1939 limits POP3 line length to
      46  # 512 characters, including CRLF. We have selected 2048 just to be on
      47  # the safe side.
      48  _MAXLINE = 2048
      49  
      50  
      51  class ESC[4;38;5;81mPOP3:
      52  
      53      """This class supports both the minimal and optional command sets.
      54      Arguments can be strings or integers (where appropriate)
      55      (e.g.: retr(1) and retr('1') both work equally well.
      56  
      57      Minimal Command Set:
      58              USER name               user(name)
      59              PASS string             pass_(string)
      60              STAT                    stat()
      61              LIST [msg]              list(msg = None)
      62              RETR msg                retr(msg)
      63              DELE msg                dele(msg)
      64              NOOP                    noop()
      65              RSET                    rset()
      66              QUIT                    quit()
      67  
      68      Optional Commands (some servers support these):
      69              RPOP name               rpop(name)
      70              APOP name digest        apop(name, digest)
      71              TOP msg n               top(msg, n)
      72              UIDL [msg]              uidl(msg = None)
      73              CAPA                    capa()
      74              STLS                    stls()
      75              UTF8                    utf8()
      76  
      77      Raises one exception: 'error_proto'.
      78  
      79      Instantiate with:
      80              POP3(hostname, port=110)
      81  
      82      NB:     the POP protocol locks the mailbox from user
      83              authorization until QUIT, so be sure to get in, suck
      84              the messages, and quit, each time you access the
      85              mailbox.
      86  
      87              POP is a line-based protocol, which means large mail
      88              messages consume lots of python cycles reading them
      89              line-by-line.
      90  
      91              If it's available on your mail server, use IMAP4
      92              instead, it doesn't suffer from the two problems
      93              above.
      94      """
      95  
      96      encoding = 'UTF-8'
      97  
      98      def __init__(self, host, port=POP3_PORT,
      99                   timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
     100          self.host = host
     101          self.port = port
     102          self._tls_established = False
     103          sys.audit("poplib.connect", self, host, port)
     104          self.sock = self._create_socket(timeout)
     105          self.file = self.sock.makefile('rb')
     106          self._debugging = 0
     107          self.welcome = self._getresp()
     108  
     109      def _create_socket(self, timeout):
     110          if timeout is not None and not timeout:
     111              raise ValueError('Non-blocking socket (timeout=0) is not supported')
     112          return socket.create_connection((self.host, self.port), timeout)
     113  
     114      def _putline(self, line):
     115          if self._debugging > 1: print('*put*', repr(line))
     116          sys.audit("poplib.putline", self, line)
     117          self.sock.sendall(line + CRLF)
     118  
     119  
     120      # Internal: send one command to the server (through _putline())
     121  
     122      def _putcmd(self, line):
     123          if self._debugging: print('*cmd*', repr(line))
     124          line = bytes(line, self.encoding)
     125          self._putline(line)
     126  
     127  
     128      # Internal: return one line from the server, stripping CRLF.
     129      # This is where all the CPU time of this module is consumed.
     130      # Raise error_proto('-ERR EOF') if the connection is closed.
     131  
     132      def _getline(self):
     133          line = self.file.readline(_MAXLINE + 1)
     134          if len(line) > _MAXLINE:
     135              raise error_proto('line too long')
     136  
     137          if self._debugging > 1: print('*get*', repr(line))
     138          if not line: raise error_proto('-ERR EOF')
     139          octets = len(line)
     140          # server can send any combination of CR & LF
     141          # however, 'readline()' returns lines ending in LF
     142          # so only possibilities are ...LF, ...CRLF, CR...LF
     143          if line[-2:] == CRLF:
     144              return line[:-2], octets
     145          if line[:1] == CR:
     146              return line[1:-1], octets
     147          return line[:-1], octets
     148  
     149  
     150      # Internal: get a response from the server.
     151      # Raise 'error_proto' if the response doesn't start with '+'.
     152  
     153      def _getresp(self):
     154          resp, o = self._getline()
     155          if self._debugging > 1: print('*resp*', repr(resp))
     156          if not resp.startswith(b'+'):
     157              raise error_proto(resp)
     158          return resp
     159  
     160  
     161      # Internal: get a response plus following text from the server.
     162  
     163      def _getlongresp(self):
     164          resp = self._getresp()
     165          list = []; octets = 0
     166          line, o = self._getline()
     167          while line != b'.':
     168              if line.startswith(b'..'):
     169                  o = o-1
     170                  line = line[1:]
     171              octets = octets + o
     172              list.append(line)
     173              line, o = self._getline()
     174          return resp, list, octets
     175  
     176  
     177      # Internal: send a command and get the response
     178  
     179      def _shortcmd(self, line):
     180          self._putcmd(line)
     181          return self._getresp()
     182  
     183  
     184      # Internal: send a command and get the response plus following text
     185  
     186      def _longcmd(self, line):
     187          self._putcmd(line)
     188          return self._getlongresp()
     189  
     190  
     191      # These can be useful:
     192  
     193      def getwelcome(self):
     194          return self.welcome
     195  
     196  
     197      def set_debuglevel(self, level):
     198          self._debugging = level
     199  
     200  
     201      # Here are all the POP commands:
     202  
     203      def user(self, user):
     204          """Send user name, return response
     205  
     206          (should indicate password required).
     207          """
     208          return self._shortcmd('USER %s' % user)
     209  
     210  
     211      def pass_(self, pswd):
     212          """Send password, return response
     213  
     214          (response includes message count, mailbox size).
     215  
     216          NB: mailbox is locked by server from here to 'quit()'
     217          """
     218          return self._shortcmd('PASS %s' % pswd)
     219  
     220  
     221      def stat(self):
     222          """Get mailbox status.
     223  
     224          Result is tuple of 2 ints (message count, mailbox size)
     225          """
     226          retval = self._shortcmd('STAT')
     227          rets = retval.split()
     228          if self._debugging: print('*stat*', repr(rets))
     229          numMessages = int(rets[1])
     230          sizeMessages = int(rets[2])
     231          return (numMessages, sizeMessages)
     232  
     233  
     234      def list(self, which=None):
     235          """Request listing, return result.
     236  
     237          Result without a message number argument is in form
     238          ['response', ['mesg_num octets', ...], octets].
     239  
     240          Result when a message number argument is given is a
     241          single response: the "scan listing" for that message.
     242          """
     243          if which is not None:
     244              return self._shortcmd('LIST %s' % which)
     245          return self._longcmd('LIST')
     246  
     247  
     248      def retr(self, which):
     249          """Retrieve whole message number 'which'.
     250  
     251          Result is in form ['response', ['line', ...], octets].
     252          """
     253          return self._longcmd('RETR %s' % which)
     254  
     255  
     256      def dele(self, which):
     257          """Delete message number 'which'.
     258  
     259          Result is 'response'.
     260          """
     261          return self._shortcmd('DELE %s' % which)
     262  
     263  
     264      def noop(self):
     265          """Does nothing.
     266  
     267          One supposes the response indicates the server is alive.
     268          """
     269          return self._shortcmd('NOOP')
     270  
     271  
     272      def rset(self):
     273          """Unmark all messages marked for deletion."""
     274          return self._shortcmd('RSET')
     275  
     276  
     277      def quit(self):
     278          """Signoff: commit changes on server, unlock mailbox, close connection."""
     279          resp = self._shortcmd('QUIT')
     280          self.close()
     281          return resp
     282  
     283      def close(self):
     284          """Close the connection without assuming anything about it."""
     285          try:
     286              file = self.file
     287              self.file = None
     288              if file is not None:
     289                  file.close()
     290          finally:
     291              sock = self.sock
     292              self.sock = None
     293              if sock is not None:
     294                  try:
     295                      sock.shutdown(socket.SHUT_RDWR)
     296                  except OSError as exc:
     297                      # The server might already have closed the connection.
     298                      # On Windows, this may result in WSAEINVAL (error 10022):
     299                      # An invalid operation was attempted.
     300                      if (exc.errno != errno.ENOTCONN
     301                         and getattr(exc, 'winerror', 0) != 10022):
     302                          raise
     303                  finally:
     304                      sock.close()
     305  
     306      #__del__ = quit
     307  
     308  
     309      # optional commands:
     310  
     311      def rpop(self, user):
     312          """Not sure what this does."""
     313          return self._shortcmd('RPOP %s' % user)
     314  
     315  
     316      timestamp = re.compile(br'\+OK.[^<]*(<.*>)')
     317  
     318      def apop(self, user, password):
     319          """Authorisation
     320  
     321          - only possible if server has supplied a timestamp in initial greeting.
     322  
     323          Args:
     324                  user     - mailbox user;
     325                  password - mailbox password.
     326  
     327          NB: mailbox is locked by server from here to 'quit()'
     328          """
     329          secret = bytes(password, self.encoding)
     330          m = self.timestamp.match(self.welcome)
     331          if not m:
     332              raise error_proto('-ERR APOP not supported by server')
     333          import hashlib
     334          digest = m.group(1)+secret
     335          digest = hashlib.md5(digest).hexdigest()
     336          return self._shortcmd('APOP %s %s' % (user, digest))
     337  
     338  
     339      def top(self, which, howmuch):
     340          """Retrieve message header of message number 'which'
     341          and first 'howmuch' lines of message body.
     342  
     343          Result is in form ['response', ['line', ...], octets].
     344          """
     345          return self._longcmd('TOP %s %s' % (which, howmuch))
     346  
     347  
     348      def uidl(self, which=None):
     349          """Return message digest (unique id) list.
     350  
     351          If 'which', result contains unique id for that message
     352          in the form 'response mesgnum uid', otherwise result is
     353          the list ['response', ['mesgnum uid', ...], octets]
     354          """
     355          if which is not None:
     356              return self._shortcmd('UIDL %s' % which)
     357          return self._longcmd('UIDL')
     358  
     359  
     360      def utf8(self):
     361          """Try to enter UTF-8 mode (see RFC 6856). Returns server response.
     362          """
     363          return self._shortcmd('UTF8')
     364  
     365  
     366      def capa(self):
     367          """Return server capabilities (RFC 2449) as a dictionary
     368          >>> c=poplib.POP3('localhost')
     369          >>> c.capa()
     370          {'IMPLEMENTATION': ['Cyrus', 'POP3', 'server', 'v2.2.12'],
     371           'TOP': [], 'LOGIN-DELAY': ['0'], 'AUTH-RESP-CODE': [],
     372           'EXPIRE': ['NEVER'], 'USER': [], 'STLS': [], 'PIPELINING': [],
     373           'UIDL': [], 'RESP-CODES': []}
     374          >>>
     375  
     376          Really, according to RFC 2449, the cyrus folks should avoid
     377          having the implementation split into multiple arguments...
     378          """
     379          def _parsecap(line):
     380              lst = line.decode('ascii').split()
     381              return lst[0], lst[1:]
     382  
     383          caps = {}
     384          try:
     385              resp = self._longcmd('CAPA')
     386              rawcaps = resp[1]
     387              for capline in rawcaps:
     388                  capnm, capargs = _parsecap(capline)
     389                  caps[capnm] = capargs
     390          except error_proto:
     391              raise error_proto('-ERR CAPA not supported by server')
     392          return caps
     393  
     394  
     395      def stls(self, context=None):
     396          """Start a TLS session on the active connection as specified in RFC 2595.
     397  
     398                  context - a ssl.SSLContext
     399          """
     400          if not HAVE_SSL:
     401              raise error_proto('-ERR TLS support missing')
     402          if self._tls_established:
     403              raise error_proto('-ERR TLS session already established')
     404          caps = self.capa()
     405          if not 'STLS' in caps:
     406              raise error_proto('-ERR STLS not supported by server')
     407          if context is None:
     408              context = ssl._create_stdlib_context()
     409          resp = self._shortcmd('STLS')
     410          self.sock = context.wrap_socket(self.sock,
     411                                          server_hostname=self.host)
     412          self.file = self.sock.makefile('rb')
     413          self._tls_established = True
     414          return resp
     415  
     416  
     417  if HAVE_SSL:
     418  
     419      class ESC[4;38;5;81mPOP3_SSL(ESC[4;38;5;149mPOP3):
     420          """POP3 client class over SSL connection
     421  
     422          Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None,
     423                                     context=None)
     424  
     425                 hostname - the hostname of the pop3 over ssl server
     426                 port - port number
     427                 keyfile - PEM formatted file that contains your private key
     428                 certfile - PEM formatted certificate chain file
     429                 context - a ssl.SSLContext
     430  
     431          See the methods of the parent class POP3 for more documentation.
     432          """
     433  
     434          def __init__(self, host, port=POP3_SSL_PORT, keyfile=None, certfile=None,
     435                       timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None):
     436              if context is not None and keyfile is not None:
     437                  raise ValueError("context and keyfile arguments are mutually "
     438                                   "exclusive")
     439              if context is not None and certfile is not None:
     440                  raise ValueError("context and certfile arguments are mutually "
     441                                   "exclusive")
     442              if keyfile is not None or certfile is not None:
     443                  import warnings
     444                  warnings.warn("keyfile and certfile are deprecated, use a "
     445                                "custom context instead", DeprecationWarning, 2)
     446              self.keyfile = keyfile
     447              self.certfile = certfile
     448              if context is None:
     449                  context = ssl._create_stdlib_context(certfile=certfile,
     450                                                       keyfile=keyfile)
     451              self.context = context
     452              POP3.__init__(self, host, port, timeout)
     453  
     454          def _create_socket(self, timeout):
     455              sock = POP3._create_socket(self, timeout)
     456              sock = self.context.wrap_socket(sock,
     457                                              server_hostname=self.host)
     458              return sock
     459  
     460          def stls(self, keyfile=None, certfile=None, context=None):
     461              """The method unconditionally raises an exception since the
     462              STLS command doesn't make any sense on an already established
     463              SSL/TLS session.
     464              """
     465              raise error_proto('-ERR TLS session already established')
     466  
     467      __all__.append("POP3_SSL")
     468  
     469  if __name__ == "__main__":
     470      import sys
     471      a = POP3(sys.argv[1])
     472      print(a.getwelcome())
     473      a.user(sys.argv[2])
     474      a.pass_(sys.argv[3])
     475      a.list()
     476      (numMsgs, totalSize) = a.stat()
     477      for i in range(1, numMsgs + 1):
     478          (header, msg, octets) = a.retr(i)
     479          print("Message %d:" % i)
     480          for line in msg:
     481              print('   ' + line)
     482          print('-----------------------')
     483      a.quit()