poplib.py 10.8 KB
Newer Older
1
"""A POP3 client class.
2

3
Based on the J. Myers POP3 draft, Jan. 96
4 5
"""

6 7 8
# Author: David Ascher <david_ascher@brown.edu>
#         [heavily stealing from nntplib.py]
# 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 13 14 15
# Example (see the test function at the end of this file)

# Imports

16
import re, socket
17

18
__all__ = ["POP3","error_proto"]
19

20
# Exception raised when an error or invalid response is received:
21 22

class error_proto(Exception): pass
23 24 25 26

# Standard Port
POP3_PORT = 110

27 28 29
# POP SSL PORT
POP3_SSL_PORT = 995

30
# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
31 32
CR = b'\r'
LF = b'\n'
33
CRLF = CR+LF
34 35 36


class POP3:
37

Tim Peters's avatar
Tim Peters committed
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
    """This class supports both the minimal and optional command sets.
    Arguments can be strings or integers (where appropriate)
    (e.g.: retr(1) and retr('1') both work equally well.

    Minimal Command Set:
            USER name               user(name)
            PASS string             pass_(string)
            STAT                    stat()
            LIST [msg]              list(msg = None)
            RETR msg                retr(msg)
            DELE msg                dele(msg)
            NOOP                    noop()
            RSET                    rset()
            QUIT                    quit()

    Optional Commands (some servers support these):
            RPOP name               rpop(name)
            APOP name digest        apop(name, digest)
            TOP msg n               top(msg, n)
            UIDL [msg]              uidl(msg = None)

    Raises one exception: 'error_proto'.
60

Tim Peters's avatar
Tim Peters committed
61 62
    Instantiate with:
            POP3(hostname, port=110)
63

Tim Peters's avatar
Tim Peters committed
64 65 66 67
    NB:     the POP protocol locks the mailbox from user
            authorization until QUIT, so be sure to get in, suck
            the messages, and quit, each time you access the
            mailbox.
68

Tim Peters's avatar
Tim Peters committed
69 70 71
            POP is a line-based protocol, which means large mail
            messages consume lots of python cycles reading them
            line-by-line.
72

Tim Peters's avatar
Tim Peters committed
73 74 75 76
            If it's available on your mail server, use IMAP4
            instead, it doesn't suffer from the two problems
            above.
    """
77

78
    encoding = 'UTF-8'
79

Georg Brandl's avatar
Georg Brandl committed
80 81
    def __init__(self, host, port=POP3_PORT,
                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
82 83
        self.host = host
        self.port = port
84
        self.sock = self._create_socket(timeout)
85 86 87
        self.file = self.sock.makefile('rb')
        self._debugging = 0
        self.welcome = self._getresp()
88

89 90
    def _create_socket(self, timeout):
        return socket.create_connection((self.host, self.port), timeout)
91

Tim Peters's avatar
Tim Peters committed
92
    def _putline(self, line):
93
        if self._debugging > 1: print('*put*', repr(line))
94
        self.sock.sendall(line + CRLF)
95

96

Tim Peters's avatar
Tim Peters committed
97
    # Internal: send one command to the server (through _putline())
98

Tim Peters's avatar
Tim Peters committed
99
    def _putcmd(self, line):
100
        if self._debugging: print('*cmd*', repr(line))
101
        line = bytes(line, self.encoding)
Tim Peters's avatar
Tim Peters committed
102
        self._putline(line)
103

104

Tim Peters's avatar
Tim Peters committed
105 106 107
    # Internal: return one line from the server, stripping CRLF.
    # This is where all the CPU time of this module is consumed.
    # Raise error_proto('-ERR EOF') if the connection is closed.
108

Tim Peters's avatar
Tim Peters committed
109 110
    def _getline(self):
        line = self.file.readline()
111
        if self._debugging > 1: print('*get*', repr(line))
Tim Peters's avatar
Tim Peters committed
112 113 114 115 116 117 118 119 120 121
        if not line: raise error_proto('-ERR EOF')
        octets = len(line)
        # server can send any combination of CR & LF
        # however, 'readline()' returns lines ending in LF
        # so only possibilities are ...LF, ...CRLF, CR...LF
        if line[-2:] == CRLF:
            return line[:-2], octets
        if line[0] == CR:
            return line[1:-1], octets
        return line[:-1], octets
122

123

Tim Peters's avatar
Tim Peters committed
124 125
    # Internal: get a response from the server.
    # Raise 'error_proto' if the response doesn't start with '+'.
126

Tim Peters's avatar
Tim Peters committed
127 128
    def _getresp(self):
        resp, o = self._getline()
129
        if self._debugging > 1: print('*resp*', repr(resp))
130
        if not resp.startswith(b'+'):
Tim Peters's avatar
Tim Peters committed
131 132
            raise error_proto(resp)
        return resp
133

134

Tim Peters's avatar
Tim Peters committed
135
    # Internal: get a response plus following text from the server.
136

Tim Peters's avatar
Tim Peters committed
137 138 139 140
    def _getlongresp(self):
        resp = self._getresp()
        list = []; octets = 0
        line, o = self._getline()
141
        while line != b'.':
142
            if line.startswith(b'..'):
Tim Peters's avatar
Tim Peters committed
143 144 145 146 147 148
                o = o-1
                line = line[1:]
            octets = octets + o
            list.append(line)
            line, o = self._getline()
        return resp, list, octets
149

150

Tim Peters's avatar
Tim Peters committed
151
    # Internal: send a command and get the response
152

Tim Peters's avatar
Tim Peters committed
153 154 155
    def _shortcmd(self, line):
        self._putcmd(line)
        return self._getresp()
156

157

Tim Peters's avatar
Tim Peters committed
158
    # Internal: send a command and get the response plus following text
159

Tim Peters's avatar
Tim Peters committed
160 161 162
    def _longcmd(self, line):
        self._putcmd(line)
        return self._getlongresp()
163

164

Tim Peters's avatar
Tim Peters committed
165
    # These can be useful:
166

Tim Peters's avatar
Tim Peters committed
167 168
    def getwelcome(self):
        return self.welcome
169

170

Tim Peters's avatar
Tim Peters committed
171 172
    def set_debuglevel(self, level):
        self._debugging = level
173

174

Tim Peters's avatar
Tim Peters committed
175
    # Here are all the POP commands:
176

Tim Peters's avatar
Tim Peters committed
177 178
    def user(self, user):
        """Send user name, return response
179

Tim Peters's avatar
Tim Peters committed
180 181 182
        (should indicate password required).
        """
        return self._shortcmd('USER %s' % user)
183

184

Tim Peters's avatar
Tim Peters committed
185 186
    def pass_(self, pswd):
        """Send password, return response
187

Tim Peters's avatar
Tim Peters committed
188
        (response includes message count, mailbox size).
189

Tim Peters's avatar
Tim Peters committed
190 191 192
        NB: mailbox is locked by server from here to 'quit()'
        """
        return self._shortcmd('PASS %s' % pswd)
193

194

Tim Peters's avatar
Tim Peters committed
195 196
    def stat(self):
        """Get mailbox status.
197

Tim Peters's avatar
Tim Peters committed
198 199 200
        Result is tuple of 2 ints (message count, mailbox size)
        """
        retval = self._shortcmd('STAT')
201
        rets = retval.split()
202
        if self._debugging: print('*stat*', repr(rets))
203 204
        numMessages = int(rets[1])
        sizeMessages = int(rets[2])
Tim Peters's avatar
Tim Peters committed
205
        return (numMessages, sizeMessages)
206

207

Tim Peters's avatar
Tim Peters committed
208 209
    def list(self, which=None):
        """Request listing, return result.
210

Tim Peters's avatar
Tim Peters committed
211
        Result without a message number argument is in form
212
        ['response', ['mesg_num octets', ...], octets].
213

Tim Peters's avatar
Tim Peters committed
214 215 216
        Result when a message number argument is given is a
        single response: the "scan listing" for that message.
        """
217
        if which is not None:
Tim Peters's avatar
Tim Peters committed
218 219
            return self._shortcmd('LIST %s' % which)
        return self._longcmd('LIST')
220

221

Tim Peters's avatar
Tim Peters committed
222 223
    def retr(self, which):
        """Retrieve whole message number 'which'.
224

Tim Peters's avatar
Tim Peters committed
225 226 227
        Result is in form ['response', ['line', ...], octets].
        """
        return self._longcmd('RETR %s' % which)
228

229

Tim Peters's avatar
Tim Peters committed
230 231
    def dele(self, which):
        """Delete message number 'which'.
232

Tim Peters's avatar
Tim Peters committed
233 234 235
        Result is 'response'.
        """
        return self._shortcmd('DELE %s' % which)
236

237

Tim Peters's avatar
Tim Peters committed
238 239
    def noop(self):
        """Does nothing.
240

Tim Peters's avatar
Tim Peters committed
241 242 243
        One supposes the response indicates the server is alive.
        """
        return self._shortcmd('NOOP')
244

245

Tim Peters's avatar
Tim Peters committed
246
    def rset(self):
Benjamin Peterson's avatar
Benjamin Peterson committed
247
        """Unmark all messages marked for deletion."""
Tim Peters's avatar
Tim Peters committed
248
        return self._shortcmd('RSET')
249 250


Tim Peters's avatar
Tim Peters committed
251 252
    def quit(self):
        """Signoff: commit changes on server, unlock mailbox, close connection."""
253 254
        resp = self._shortcmd('QUIT')
        self.close()
Tim Peters's avatar
Tim Peters committed
255
        return resp
256

257 258 259 260 261 262 263 264
    def close(self):
        """Close the connection without assuming anything about it."""
        if self.file is not None:
            self.file.close()
        if self.sock is not None:
            self.sock.close()
        self.file = self.sock = None

Tim Peters's avatar
Tim Peters committed
265
    #__del__ = quit
266 267


Tim Peters's avatar
Tim Peters committed
268
    # optional commands:
269

Tim Peters's avatar
Tim Peters committed
270 271 272
    def rpop(self, user):
        """Not sure what this does."""
        return self._shortcmd('RPOP %s' % user)
273 274


275
    timestamp = re.compile(br'\+OK.*(<[^>]+>)')
276

277
    def apop(self, user, password):
Tim Peters's avatar
Tim Peters committed
278
        """Authorisation
279

Tim Peters's avatar
Tim Peters committed
280
        - only possible if server has supplied a timestamp in initial greeting.
281

Tim Peters's avatar
Tim Peters committed
282
        Args:
283 284
                user     - mailbox user;
                password - mailbox password.
285

Tim Peters's avatar
Tim Peters committed
286 287
        NB: mailbox is locked by server from here to 'quit()'
        """
288
        secret = bytes(password, self.encoding)
289 290
        m = self.timestamp.match(self.welcome)
        if not m:
Tim Peters's avatar
Tim Peters committed
291
            raise error_proto('-ERR APOP not supported by server')
292
        import hashlib
293 294
        digest = m.group(1)+secret
        digest = hashlib.md5(digest).hexdigest()
Tim Peters's avatar
Tim Peters committed
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
        return self._shortcmd('APOP %s %s' % (user, digest))


    def top(self, which, howmuch):
        """Retrieve message header of message number 'which'
        and first 'howmuch' lines of message body.

        Result is in form ['response', ['line', ...], octets].
        """
        return self._longcmd('TOP %s %s' % (which, howmuch))


    def uidl(self, which=None):
        """Return message digest (unique id) list.

        If 'which', result contains unique id for that message
        in the form 'response mesgnum uid', otherwise result is
        the list ['response', ['mesgnum uid', ...], octets]
        """
314
        if which is not None:
Tim Peters's avatar
Tim Peters committed
315 316
            return self._shortcmd('UIDL %s' % which)
        return self._longcmd('UIDL')
317

318 319 320 321 322
try:
    import ssl
except ImportError:
    pass
else:
323

324 325
    class POP3_SSL(POP3):
        """POP3 client class over SSL connection
326

327
        Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None)
328

329 330 331 332
               hostname - the hostname of the pop3 over ssl server
               port - port number
               keyfile - PEM formatted file that countains your private key
               certfile - PEM formatted certificate chain file
333

334
        See the methods of the parent class POP3 for more documentation.
335
        """
336

337 338 339 340 341 342 343 344
        def __init__(self, host, port=POP3_SSL_PORT, keyfile=None, certfile=None,
                     timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context=None):
            if context is not None and keyfile is not None:
                raise ValueError("context and keyfile arguments are mutually "
                                 "exclusive")
            if context is not None and certfile is not None:
                raise ValueError("context and certfile arguments are mutually "
                                 "exclusive")
345 346
            self.keyfile = keyfile
            self.certfile = certfile
347
            self.context = context
348 349 350 351
            POP3.__init__(self, host, port, timeout)

        def _create_socket(self, timeout):
            sock = POP3._create_socket(self, timeout)
352 353 354 355 356
            if self.context is not None:
                sock = self.context.wrap_socket(sock)
            else:
                sock = ssl.wrap_socket(sock, self.keyfile, self.certfile)
            return sock
357 358

    __all__.append("POP3_SSL")
359

360
if __name__ == "__main__":
361 362
    import sys
    a = POP3(sys.argv[1])
363
    print(a.getwelcome())
364 365
    a.user(sys.argv[2])
    a.pass_(sys.argv[3])
Tim Peters's avatar
Tim Peters committed
366 367 368 369
    a.list()
    (numMsgs, totalSize) = a.stat()
    for i in range(1, numMsgs + 1):
        (header, msg, octets) = a.retr(i)
370
        print("Message %d:" % i)
Tim Peters's avatar
Tim Peters committed
371
        for line in msg:
372 373
            print('   ' + line)
        print('-----------------------')
Tim Peters's avatar
Tim Peters committed
374
    a.quit()