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