poplib.py revision 1b1c347311edcddac3381b6c2cfe2f86e82e2d60
1"""A POP3 client class.
2
3Based 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
16import re, socket
17
18__all__ = ["POP3","error_proto","POP3_SSL"]
19
20# Exception raised when an error or invalid response is received:
21
22class error_proto(Exception): pass
23
24# Standard Port
25POP3_PORT = 110
26
27# POP SSL PORT
28POP3_SSL_PORT = 995
29
30# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
31CR = '\r'
32LF = '\n'
33CRLF = CR+LF
34
35
36class POP3:
37
38    """This class supports both the minimal and optional command sets.
39    Arguments can be strings or integers (where appropriate)
40    (e.g.: retr(1) and retr('1') both work equally well.
41
42    Minimal Command Set:
43            USER name               user(name)
44            PASS string             pass_(string)
45            STAT                    stat()
46            LIST [msg]              list(msg = None)
47            RETR msg                retr(msg)
48            DELE msg                dele(msg)
49            NOOP                    noop()
50            RSET                    rset()
51            QUIT                    quit()
52
53    Optional Commands (some servers support these):
54            RPOP name               rpop(name)
55            APOP name digest        apop(name, digest)
56            TOP msg n               top(msg, n)
57            UIDL [msg]              uidl(msg = None)
58
59    Raises one exception: 'error_proto'.
60
61    Instantiate with:
62            POP3(hostname, port=110)
63
64    NB:     the POP protocol locks the mailbox from user
65            authorization until QUIT, so be sure to get in, suck
66            the messages, and quit, each time you access the
67            mailbox.
68
69            POP is a line-based protocol, which means large mail
70            messages consume lots of python cycles reading them
71            line-by-line.
72
73            If it's available on your mail server, use IMAP4
74            instead, it doesn't suffer from the two problems
75            above.
76    """
77
78
79    def __init__(self, host, port=POP3_PORT, timeout=None):
80        self.host = host
81        self.port = port
82        self.sock = socket.create_connection((host, port), timeout)
83        self.file = self.sock.makefile('rb')
84        self._debugging = 0
85        self.welcome = self._getresp()
86
87
88    def _putline(self, line):
89        if self._debugging > 1: print '*put*', repr(line)
90        self.sock.sendall('%s%s' % (line, CRLF))
91
92
93    # Internal: send one command to the server (through _putline())
94
95    def _putcmd(self, line):
96        if self._debugging: print '*cmd*', repr(line)
97        self._putline(line)
98
99
100    # Internal: return one line from the server, stripping CRLF.
101    # This is where all the CPU time of this module is consumed.
102    # Raise error_proto('-ERR EOF') if the connection is closed.
103
104    def _getline(self):
105        line = self.file.readline()
106        if self._debugging > 1: print '*get*', repr(line)
107        if not line: raise error_proto('-ERR EOF')
108        octets = len(line)
109        # server can send any combination of CR & LF
110        # however, 'readline()' returns lines ending in LF
111        # so only possibilities are ...LF, ...CRLF, CR...LF
112        if line[-2:] == CRLF:
113            return line[:-2], octets
114        if line[0] == CR:
115            return line[1:-1], octets
116        return line[:-1], octets
117
118
119    # Internal: get a response from the server.
120    # Raise 'error_proto' if the response doesn't start with '+'.
121
122    def _getresp(self):
123        resp, o = self._getline()
124        if self._debugging > 1: print '*resp*', repr(resp)
125        c = resp[:1]
126        if c != '+':
127            raise error_proto(resp)
128        return resp
129
130
131    # Internal: get a response plus following text from the server.
132
133    def _getlongresp(self):
134        resp = self._getresp()
135        list = []; octets = 0
136        line, o = self._getline()
137        while line != '.':
138            if line[:2] == '..':
139                o = o-1
140                line = line[1:]
141            octets = octets + o
142            list.append(line)
143            line, o = self._getline()
144        return resp, list, octets
145
146
147    # Internal: send a command and get the response
148
149    def _shortcmd(self, line):
150        self._putcmd(line)
151        return self._getresp()
152
153
154    # Internal: send a command and get the response plus following text
155
156    def _longcmd(self, line):
157        self._putcmd(line)
158        return self._getlongresp()
159
160
161    # These can be useful:
162
163    def getwelcome(self):
164        return self.welcome
165
166
167    def set_debuglevel(self, level):
168        self._debugging = level
169
170
171    # Here are all the POP commands:
172
173    def user(self, user):
174        """Send user name, return response
175
176        (should indicate password required).
177        """
178        return self._shortcmd('USER %s' % user)
179
180
181    def pass_(self, pswd):
182        """Send password, return response
183
184        (response includes message count, mailbox size).
185
186        NB: mailbox is locked by server from here to 'quit()'
187        """
188        return self._shortcmd('PASS %s' % pswd)
189
190
191    def stat(self):
192        """Get mailbox status.
193
194        Result is tuple of 2 ints (message count, mailbox size)
195        """
196        retval = self._shortcmd('STAT')
197        rets = retval.split()
198        if self._debugging: print '*stat*', repr(rets)
199        numMessages = int(rets[1])
200        sizeMessages = int(rets[2])
201        return (numMessages, sizeMessages)
202
203
204    def list(self, which=None):
205        """Request listing, return result.
206
207        Result without a message number argument is in form
208        ['response', ['mesg_num octets', ...], octets].
209
210        Result when a message number argument is given is a
211        single response: the "scan listing" for that message.
212        """
213        if which is not None:
214            return self._shortcmd('LIST %s' % which)
215        return self._longcmd('LIST')
216
217
218    def retr(self, which):
219        """Retrieve whole message number 'which'.
220
221        Result is in form ['response', ['line', ...], octets].
222        """
223        return self._longcmd('RETR %s' % which)
224
225
226    def dele(self, which):
227        """Delete message number 'which'.
228
229        Result is 'response'.
230        """
231        return self._shortcmd('DELE %s' % which)
232
233
234    def noop(self):
235        """Does nothing.
236
237        One supposes the response indicates the server is alive.
238        """
239        return self._shortcmd('NOOP')
240
241
242    def rset(self):
243        """Not sure what this does."""
244        return self._shortcmd('RSET')
245
246
247    def quit(self):
248        """Signoff: commit changes on server, unlock mailbox, close connection."""
249        try:
250            resp = self._shortcmd('QUIT')
251        except error_proto, val:
252            resp = val
253        self.file.close()
254        self.sock.close()
255        del self.file, self.sock
256        return resp
257
258    #__del__ = quit
259
260
261    # optional commands:
262
263    def rpop(self, user):
264        """Not sure what this does."""
265        return self._shortcmd('RPOP %s' % user)
266
267
268    timestamp = re.compile(r'\+OK.*(<[^>]+>)')
269
270    def apop(self, user, secret):
271        """Authorisation
272
273        - only possible if server has supplied a timestamp in initial greeting.
274
275        Args:
276                user    - mailbox user;
277                secret  - secret shared between client and server.
278
279        NB: mailbox is locked by server from here to 'quit()'
280        """
281        m = self.timestamp.match(self.welcome)
282        if not m:
283            raise error_proto('-ERR APOP not supported by server')
284        import hashlib
285        digest = hashlib.md5(m.group(1)+secret).digest()
286        digest = ''.join(map(lambda x:'%02x'%ord(x), digest))
287        return self._shortcmd('APOP %s %s' % (user, digest))
288
289
290    def top(self, which, howmuch):
291        """Retrieve message header of message number 'which'
292        and first 'howmuch' lines of message body.
293
294        Result is in form ['response', ['line', ...], octets].
295        """
296        return self._longcmd('TOP %s %s' % (which, howmuch))
297
298
299    def uidl(self, which=None):
300        """Return message digest (unique id) list.
301
302        If 'which', result contains unique id for that message
303        in the form 'response mesgnum uid', otherwise result is
304        the list ['response', ['mesgnum uid', ...], octets]
305        """
306        if which is not None:
307            return self._shortcmd('UIDL %s' % which)
308        return self._longcmd('UIDL')
309
310class POP3_SSL(POP3):
311    """POP3 client class over SSL connection
312
313    Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None)
314
315           hostname - the hostname of the pop3 over ssl server
316           port - port number
317           keyfile - PEM formatted file that countains your private key
318           certfile - PEM formatted certificate chain file
319
320        See the methods of the parent class POP3 for more documentation.
321    """
322
323    def __init__(self, host, port = POP3_SSL_PORT, keyfile = None, certfile = None):
324        self.host = host
325        self.port = port
326        self.keyfile = keyfile
327        self.certfile = certfile
328        self.buffer = ""
329        msg = "getaddrinfo returns an empty list"
330        self.sock = None
331        for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
332            af, socktype, proto, canonname, sa = res
333            try:
334                self.sock = socket.socket(af, socktype, proto)
335                self.sock.connect(sa)
336            except socket.error, msg:
337                if self.sock:
338                    self.sock.close()
339                self.sock = None
340                continue
341            break
342        if not self.sock:
343            raise socket.error, msg
344        self.file = self.sock.makefile('rb')
345        self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile)
346        self._debugging = 0
347        self.welcome = self._getresp()
348
349    def _fillBuffer(self):
350        localbuf = self.sslobj.read()
351        if len(localbuf) == 0:
352            raise error_proto('-ERR EOF')
353        self.buffer += localbuf
354
355    def _getline(self):
356        line = ""
357        renewline = re.compile(r'.*?\n')
358        match = renewline.match(self.buffer)
359        while not match:
360            self._fillBuffer()
361            match = renewline.match(self.buffer)
362        line = match.group(0)
363        self.buffer = renewline.sub('' ,self.buffer, 1)
364        if self._debugging > 1: print '*get*', repr(line)
365
366        octets = len(line)
367        if line[-2:] == CRLF:
368            return line[:-2], octets
369        if line[0] == CR:
370            return line[1:-1], octets
371        return line[:-1], octets
372
373    def _putline(self, line):
374        if self._debugging > 1: print '*put*', repr(line)
375        line += CRLF
376        bytes = len(line)
377        while bytes > 0:
378            sent = self.sslobj.write(line)
379            if sent == bytes:
380                break    # avoid copy
381            line = line[sent:]
382            bytes = bytes - sent
383
384    def quit(self):
385        """Signoff: commit changes on server, unlock mailbox, close connection."""
386        try:
387            resp = self._shortcmd('QUIT')
388        except error_proto, val:
389            resp = val
390        self.sock.close()
391        del self.sslobj, self.sock
392        return resp
393
394
395if __name__ == "__main__":
396    import sys
397    a = POP3(sys.argv[1])
398    print a.getwelcome()
399    a.user(sys.argv[2])
400    a.pass_(sys.argv[3])
401    a.list()
402    (numMsgs, totalSize) = a.stat()
403    for i in range(1, numMsgs + 1):
404        (header, msg, octets) = a.retr(i)
405        print "Message %d:" % i
406        for line in msg:
407            print '   ' + line
408        print '-----------------------'
409    a.quit()
410