Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
poplib.py478 linesDownload Raw Back to Lib
1"""A POP3 client class.2 3Based on the J. Myers POP3 draft, Jan. 964"""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 200311 12# Example (see the test function at the end of this file)13 14# Imports15 16import errno17import re18import socket19import sys20 21try:22    import ssl23    HAVE_SSL = True24except ImportError:25    HAVE_SSL = False26 27__all__ = ["POP3","error_proto"]28 29# Exception raised when an error or invalid response is received:30 31class error_proto(Exception): pass32 33# Standard Port34POP3_PORT = 11035 36# POP SSL PORT37POP3_SSL_PORT = 99538 39# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)40CR = b'\r'41LF = b'\n'42CRLF = CR+LF43 44# maximal line length when calling readline(). This is to prevent45# reading arbitrary length lines. RFC 1939 limits POP3 line length to46# 512 characters, including CRLF. We have selected 2048 just to be on47# the safe side.48_MAXLINE = 204849 50 51class POP3: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 user83            authorization until QUIT, so be sure to get in, suck84            the messages, and quit, each time you access the85            mailbox.86 87            POP is a line-based protocol, which means large mail88            messages consume lots of python cycles reading them89            line-by-line.90 91            If it's available on your mail server, use IMAP492            instead, it doesn't suffer from the two problems93            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 = host101        self.port = port102        self._tls_established = False103        sys.audit("poplib.connect", self, host, port)104        self.sock = self._create_socket(timeout)105        self.file = self.sock.makefile('rb')106        self._debugging = 0107        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 & LF141        # however, 'readline()' returns lines ending in LF142        # so only possibilities are ...LF, ...CRLF, CR...LF143        if line[-2:] == CRLF:144            return line[:-2], octets145        if line[:1] == CR:146            return line[1:-1], octets147        return line[:-1], octets148 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 resp159 160 161    # Internal: get a response plus following text from the server.162 163    def _getlongresp(self):164        resp = self._getresp()165        list = []; octets = 0166        line, o = self._getline()167        while line != b'.':168            if line.startswith(b'..'):169                o = o-1170                line = line[1:]171            octets = octets + o172            list.append(line)173            line, o = self._getline()174        return resp, list, octets175 176 177    # Internal: send a command and get the response178 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 text185 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.welcome195 196 197    def set_debuglevel(self, level):198        self._debugging = level199 200 201    # Here are all the POP commands:202 203    def user(self, user):204        """Send user name, return response205 206        (should indicate password required).207        """208        return self._shortcmd('USER %s' % user)209 210 211    def pass_(self, pswd):212        """Send password, return response213 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 230        # Check if the response has enough elements231        # RFC 1939 requires at least 3 elements (+OK, message count, mailbox size)232        # but allows additional data after the required fields233        if len(rets) < 3:234            raise error_proto("Invalid STAT response format")235 236        try:237            numMessages = int(rets[1])238            sizeMessages = int(rets[2])239        except ValueError:240            raise error_proto("Invalid STAT response data: non-numeric values")241 242        return (numMessages, sizeMessages)243 244 245    def list(self, which=None):246        """Request listing, return result.247 248        Result without a message number argument is in form249        ['response', ['mesg_num octets', ...], octets].250 251        Result when a message number argument is given is a252        single response: the "scan listing" for that message.253        """254        if which is not None:255            return self._shortcmd('LIST %s' % which)256        return self._longcmd('LIST')257 258 259    def retr(self, which):260        """Retrieve whole message number 'which'.261 262        Result is in form ['response', ['line', ...], octets].263        """264        return self._longcmd('RETR %s' % which)265 266 267    def dele(self, which):268        """Delete message number 'which'.269 270        Result is 'response'.271        """272        return self._shortcmd('DELE %s' % which)273 274 275    def noop(self):276        """Does nothing.277 278        One supposes the response indicates the server is alive.279        """280        return self._shortcmd('NOOP')281 282 283    def rset(self):284        """Unmark all messages marked for deletion."""285        return self._shortcmd('RSET')286 287 288    def quit(self):289        """Signoff: commit changes on server, unlock mailbox, close connection."""290        resp = self._shortcmd('QUIT')291        self.close()292        return resp293 294    def close(self):295        """Close the connection without assuming anything about it."""296        try:297            file = self.file298            self.file = None299            if file is not None:300                file.close()301        finally:302            sock = self.sock303            self.sock = None304            if sock is not None:305                try:306                    sock.shutdown(socket.SHUT_RDWR)307                except OSError as exc:308                    # The server might already have closed the connection.309                    # On Windows, this may result in WSAEINVAL (error 10022):310                    # An invalid operation was attempted.311                    if (exc.errno != errno.ENOTCONN312                       and getattr(exc, 'winerror', 0) != 10022):313                        raise314                finally:315                    sock.close()316 317    #__del__ = quit318 319 320    # optional commands:321 322    def rpop(self, user):323        """Send RPOP command to access the mailbox with an alternate user."""324        return self._shortcmd('RPOP %s' % user)325 326 327    timestamp = re.compile(br'\+OK.[^<]*(<.*>)')328 329    def apop(self, user, password):330        """Authorisation331 332        - only possible if server has supplied a timestamp in initial greeting.333 334        Args:335                user     - mailbox user;336                password - mailbox password.337 338        NB: mailbox is locked by server from here to 'quit()'339        """340        secret = bytes(password, self.encoding)341        m = self.timestamp.match(self.welcome)342        if not m:343            raise error_proto('-ERR APOP not supported by server')344        import hashlib345        digest = m.group(1)+secret346        digest = hashlib.md5(digest).hexdigest()347        return self._shortcmd('APOP %s %s' % (user, digest))348 349 350    def top(self, which, howmuch):351        """Retrieve message header of message number 'which'352        and first 'howmuch' lines of message body.353 354        Result is in form ['response', ['line', ...], octets].355        """356        return self._longcmd('TOP %s %s' % (which, howmuch))357 358 359    def uidl(self, which=None):360        """Return message digest (unique id) list.361 362        If 'which', result contains unique id for that message363        in the form 'response mesgnum uid', otherwise result is364        the list ['response', ['mesgnum uid', ...], octets]365        """366        if which is not None:367            return self._shortcmd('UIDL %s' % which)368        return self._longcmd('UIDL')369 370 371    def utf8(self):372        """Try to enter UTF-8 mode (see RFC 6856). Returns server response.373        """374        return self._shortcmd('UTF8')375 376 377    def capa(self):378        """Return server capabilities (RFC 2449) as a dictionary379        >>> c=poplib.POP3('localhost')380        >>> c.capa()381        {'IMPLEMENTATION': ['Cyrus', 'POP3', 'server', 'v2.2.12'],382         'TOP': [], 'LOGIN-DELAY': ['0'], 'AUTH-RESP-CODE': [],383         'EXPIRE': ['NEVER'], 'USER': [], 'STLS': [], 'PIPELINING': [],384         'UIDL': [], 'RESP-CODES': []}385        >>>386 387        Really, according to RFC 2449, the cyrus folks should avoid388        having the implementation split into multiple arguments...389        """390        def _parsecap(line):391            lst = line.decode('ascii').split()392            return lst[0], lst[1:]393 394        caps = {}395        try:396            resp = self._longcmd('CAPA')397            rawcaps = resp[1]398            for capline in rawcaps:399                capnm, capargs = _parsecap(capline)400                caps[capnm] = capargs401        except error_proto:402            raise error_proto('-ERR CAPA not supported by server')403        return caps404 405 406    def stls(self, context=None):407        """Start a TLS session on the active connection as specified in RFC 2595.408 409                context - a ssl.SSLContext410        """411        if not HAVE_SSL:412            raise error_proto('-ERR TLS support missing')413        if self._tls_established:414            raise error_proto('-ERR TLS session already established')415        caps = self.capa()416        if not 'STLS' in caps:417            raise error_proto('-ERR STLS not supported by server')418        if context is None:419            context = ssl._create_stdlib_context()420        resp = self._shortcmd('STLS')421        self.sock = context.wrap_socket(self.sock,422                                        server_hostname=self.host)423        self.file = self.sock.makefile('rb')424        self._tls_established = True425        return resp426 427 428if HAVE_SSL:429 430    class POP3_SSL(POP3):431        """POP3 client class over SSL connection432 433        Instantiate with: POP3_SSL(hostname, port=995, context=None)434 435               hostname - the hostname of the pop3 over ssl server436               port - port number437               context - a ssl.SSLContext438 439        See the methods of the parent class POP3 for more documentation.440        """441 442        def __init__(self, host, port=POP3_SSL_PORT,443                     *, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None):444            if context is None:445                context = ssl._create_stdlib_context()446            self.context = context447            POP3.__init__(self, host, port, timeout)448 449        def _create_socket(self, timeout):450            sock = POP3._create_socket(self, timeout)451            sock = self.context.wrap_socket(sock,452                                            server_hostname=self.host)453            return sock454 455        def stls(self, context=None):456            """The method unconditionally raises an exception since the457            STLS command doesn't make any sense on an already established458            SSL/TLS session.459            """460            raise error_proto('-ERR TLS session already established')461 462    __all__.append("POP3_SSL")463 464if __name__ == "__main__":465    a = POP3(sys.argv[1])466    print(a.getwelcome())467    a.user(sys.argv[2])468    a.pass_(sys.argv[3])469    a.list()470    (numMsgs, totalSize) = a.stat()471    for i in range(1, numMsgs + 1):472        (header, msg, octets) = a.retr(i)473        print("Message %d:" % i)474        for line in msg:475            print('   ' + line)476        print('-----------------------')477    a.quit()478 
codekingpro/portable-devtools · Team Ai