blob: c67cbc67644f38d100adba031163c138ca090696 [file] [log] [blame]
Guido van Rossum03774bb1998-04-09 13:50:55 +00001"""A POP3 client class.
Guido van Rossum484772d1998-04-06 18:27:27 +00002
Guido van Rossum03774bb1998-04-09 13:50:55 +00003Based on the J. Myers POP3 draft, Jan. 96
Guido van Rossum484772d1998-04-06 18:27:27 +00004"""
5
Guido van Rossum98d9fd32000-02-28 15:12:25 +00006# Author: David Ascher <david_ascher@brown.edu>
7# [heavily stealing from nntplib.py]
8# Updated: Piers Lauder <piers@cs.su.oz.au> [Jul '97]
Eric S. Raymond341f9292001-02-09 06:56:56 +00009# String method conversion and test jig improvements by ESR, February 2001.
Guido van Rossum98d9fd32000-02-28 15:12:25 +000010
Guido van Rossum484772d1998-04-06 18:27:27 +000011# Example (see the test function at the end of this file)
12
Guido van Rossum484772d1998-04-06 18:27:27 +000013# Imports
14
Eric S. Raymond341f9292001-02-09 06:56:56 +000015import re, socket
Guido van Rossum484772d1998-04-06 18:27:27 +000016
17# Exception raised when an error or invalid response is received:
Guido van Rossum03774bb1998-04-09 13:50:55 +000018
19class error_proto(Exception): pass
Guido van Rossum484772d1998-04-06 18:27:27 +000020
21# Standard Port
22POP3_PORT = 110
23
Guido van Rossum03774bb1998-04-09 13:50:55 +000024# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
25CR = '\r'
26LF = '\n'
27CRLF = CR+LF
Guido van Rossum484772d1998-04-06 18:27:27 +000028
29
30class POP3:
Guido van Rossum03774bb1998-04-09 13:50:55 +000031
Tim Peters2344fae2001-01-15 00:50:52 +000032 """This class supports both the minimal and optional command sets.
33 Arguments can be strings or integers (where appropriate)
34 (e.g.: retr(1) and retr('1') both work equally well.
Guido van Rossum03774bb1998-04-09 13:50:55 +000035
Tim Peters2344fae2001-01-15 00:50:52 +000036 Minimal Command Set:
37 USER name user(name)
38 PASS string pass_(string)
39 STAT stat()
40 LIST [msg] list(msg = None)
41 RETR msg retr(msg)
42 DELE msg dele(msg)
43 NOOP noop()
44 RSET rset()
45 QUIT quit()
Guido van Rossum03774bb1998-04-09 13:50:55 +000046
Tim Peters2344fae2001-01-15 00:50:52 +000047 Optional Commands (some servers support these):
48 RPOP name rpop(name)
49 APOP name digest apop(name, digest)
50 TOP msg n top(msg, n)
51 UIDL [msg] uidl(msg = None)
Guido van Rossum03774bb1998-04-09 13:50:55 +000052
Tim Peters2344fae2001-01-15 00:50:52 +000053 Raises one exception: 'error_proto'.
Guido van Rossum03774bb1998-04-09 13:50:55 +000054
Tim Peters2344fae2001-01-15 00:50:52 +000055 Instantiate with:
56 POP3(hostname, port=110)
Guido van Rossum03774bb1998-04-09 13:50:55 +000057
Tim Peters2344fae2001-01-15 00:50:52 +000058 NB: the POP protocol locks the mailbox from user
59 authorization until QUIT, so be sure to get in, suck
60 the messages, and quit, each time you access the
61 mailbox.
Guido van Rossum03774bb1998-04-09 13:50:55 +000062
Tim Peters2344fae2001-01-15 00:50:52 +000063 POP is a line-based protocol, which means large mail
64 messages consume lots of python cycles reading them
65 line-by-line.
Guido van Rossum03774bb1998-04-09 13:50:55 +000066
Tim Peters2344fae2001-01-15 00:50:52 +000067 If it's available on your mail server, use IMAP4
68 instead, it doesn't suffer from the two problems
69 above.
70 """
Guido van Rossum03774bb1998-04-09 13:50:55 +000071
72
Tim Peters2344fae2001-01-15 00:50:52 +000073 def __init__(self, host, port = POP3_PORT):
74 self.host = host
75 self.port = port
76 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
77 self.sock.connect((self.host, self.port))
78 self.file = self.sock.makefile('rb')
79 self._debugging = 0
80 self.welcome = self._getresp()
Guido van Rossum484772d1998-04-06 18:27:27 +000081
Guido van Rossum03774bb1998-04-09 13:50:55 +000082
Tim Peters2344fae2001-01-15 00:50:52 +000083 def _putline(self, line):
84 #if self._debugging > 1: print '*put*', `line`
85 self.sock.send('%s%s' % (line, CRLF))
Guido van Rossum03774bb1998-04-09 13:50:55 +000086
Guido van Rossum484772d1998-04-06 18:27:27 +000087
Tim Peters2344fae2001-01-15 00:50:52 +000088 # Internal: send one command to the server (through _putline())
Guido van Rossum03774bb1998-04-09 13:50:55 +000089
Tim Peters2344fae2001-01-15 00:50:52 +000090 def _putcmd(self, line):
91 #if self._debugging: print '*cmd*', `line`
92 self._putline(line)
Guido van Rossum484772d1998-04-06 18:27:27 +000093
Guido van Rossum03774bb1998-04-09 13:50:55 +000094
Tim Peters2344fae2001-01-15 00:50:52 +000095 # Internal: return one line from the server, stripping CRLF.
96 # This is where all the CPU time of this module is consumed.
97 # Raise error_proto('-ERR EOF') if the connection is closed.
Guido van Rossum03774bb1998-04-09 13:50:55 +000098
Tim Peters2344fae2001-01-15 00:50:52 +000099 def _getline(self):
100 line = self.file.readline()
101 #if self._debugging > 1: print '*get*', `line`
102 if not line: raise error_proto('-ERR EOF')
103 octets = len(line)
104 # server can send any combination of CR & LF
105 # however, 'readline()' returns lines ending in LF
106 # so only possibilities are ...LF, ...CRLF, CR...LF
107 if line[-2:] == CRLF:
108 return line[:-2], octets
109 if line[0] == CR:
110 return line[1:-1], octets
111 return line[:-1], octets
Guido van Rossum03774bb1998-04-09 13:50:55 +0000112
Guido van Rossum484772d1998-04-06 18:27:27 +0000113
Tim Peters2344fae2001-01-15 00:50:52 +0000114 # Internal: get a response from the server.
115 # Raise 'error_proto' if the response doesn't start with '+'.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000116
Tim Peters2344fae2001-01-15 00:50:52 +0000117 def _getresp(self):
118 resp, o = self._getline()
119 #if self._debugging > 1: print '*resp*', `resp`
120 c = resp[:1]
121 if c != '+':
122 raise error_proto(resp)
123 return resp
Guido van Rossum484772d1998-04-06 18:27:27 +0000124
Guido van Rossum03774bb1998-04-09 13:50:55 +0000125
Tim Peters2344fae2001-01-15 00:50:52 +0000126 # Internal: get a response plus following text from the server.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000127
Tim Peters2344fae2001-01-15 00:50:52 +0000128 def _getlongresp(self):
129 resp = self._getresp()
130 list = []; octets = 0
131 line, o = self._getline()
132 while line != '.':
133 if line[:2] == '..':
134 o = o-1
135 line = line[1:]
136 octets = octets + o
137 list.append(line)
138 line, o = self._getline()
139 return resp, list, octets
Guido van Rossum03774bb1998-04-09 13:50:55 +0000140
Guido van Rossum484772d1998-04-06 18:27:27 +0000141
Tim Peters2344fae2001-01-15 00:50:52 +0000142 # Internal: send a command and get the response
Guido van Rossum03774bb1998-04-09 13:50:55 +0000143
Tim Peters2344fae2001-01-15 00:50:52 +0000144 def _shortcmd(self, line):
145 self._putcmd(line)
146 return self._getresp()
Guido van Rossum484772d1998-04-06 18:27:27 +0000147
Guido van Rossum03774bb1998-04-09 13:50:55 +0000148
Tim Peters2344fae2001-01-15 00:50:52 +0000149 # Internal: send a command and get the response plus following text
Guido van Rossum03774bb1998-04-09 13:50:55 +0000150
Tim Peters2344fae2001-01-15 00:50:52 +0000151 def _longcmd(self, line):
152 self._putcmd(line)
153 return self._getlongresp()
Guido van Rossum484772d1998-04-06 18:27:27 +0000154
Guido van Rossum03774bb1998-04-09 13:50:55 +0000155
Tim Peters2344fae2001-01-15 00:50:52 +0000156 # These can be useful:
157
158 def getwelcome(self):
159 return self.welcome
160
161
162 def set_debuglevel(self, level):
163 self._debugging = level
164
165
166 # Here are all the POP commands:
167
168 def user(self, user):
169 """Send user name, return response
Guido van Rossum484772d1998-04-06 18:27:27 +0000170
Tim Peters2344fae2001-01-15 00:50:52 +0000171 (should indicate password required).
172 """
173 return self._shortcmd('USER %s' % user)
Guido van Rossum484772d1998-04-06 18:27:27 +0000174
Guido van Rossum03774bb1998-04-09 13:50:55 +0000175
Tim Peters2344fae2001-01-15 00:50:52 +0000176 def pass_(self, pswd):
177 """Send password, return response
Guido van Rossum484772d1998-04-06 18:27:27 +0000178
Tim Peters2344fae2001-01-15 00:50:52 +0000179 (response includes message count, mailbox size).
Guido van Rossum03774bb1998-04-09 13:50:55 +0000180
Tim Peters2344fae2001-01-15 00:50:52 +0000181 NB: mailbox is locked by server from here to 'quit()'
182 """
183 return self._shortcmd('PASS %s' % pswd)
Guido van Rossum484772d1998-04-06 18:27:27 +0000184
Guido van Rossum03774bb1998-04-09 13:50:55 +0000185
Tim Peters2344fae2001-01-15 00:50:52 +0000186 def stat(self):
187 """Get mailbox status.
Guido van Rossum484772d1998-04-06 18:27:27 +0000188
Tim Peters2344fae2001-01-15 00:50:52 +0000189 Result is tuple of 2 ints (message count, mailbox size)
190 """
191 retval = self._shortcmd('STAT')
Eric S. Raymond341f9292001-02-09 06:56:56 +0000192 rets = retval.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000193 #if self._debugging: print '*stat*', `rets`
Eric S. Raymond341f9292001-02-09 06:56:56 +0000194 numMessages = int(rets[1])
195 sizeMessages = int(rets[2])
Tim Peters2344fae2001-01-15 00:50:52 +0000196 return (numMessages, sizeMessages)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000197
Guido van Rossum03774bb1998-04-09 13:50:55 +0000198
Tim Peters2344fae2001-01-15 00:50:52 +0000199 def list(self, which=None):
200 """Request listing, return result.
Guido van Rossum484772d1998-04-06 18:27:27 +0000201
Tim Peters2344fae2001-01-15 00:50:52 +0000202 Result without a message number argument is in form
203 ['response', ['mesg_num octets', ...]].
Guido van Rossum484772d1998-04-06 18:27:27 +0000204
Tim Peters2344fae2001-01-15 00:50:52 +0000205 Result when a message number argument is given is a
206 single response: the "scan listing" for that message.
207 """
208 if which:
209 return self._shortcmd('LIST %s' % which)
210 return self._longcmd('LIST')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000211
Guido van Rossum03774bb1998-04-09 13:50:55 +0000212
Tim Peters2344fae2001-01-15 00:50:52 +0000213 def retr(self, which):
214 """Retrieve whole message number 'which'.
Guido van Rossumf6ae7431998-09-02 14:42:02 +0000215
Tim Peters2344fae2001-01-15 00:50:52 +0000216 Result is in form ['response', ['line', ...], octets].
217 """
218 return self._longcmd('RETR %s' % which)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000219
Guido van Rossum484772d1998-04-06 18:27:27 +0000220
Tim Peters2344fae2001-01-15 00:50:52 +0000221 def dele(self, which):
222 """Delete message number 'which'.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000223
Tim Peters2344fae2001-01-15 00:50:52 +0000224 Result is 'response'.
225 """
226 return self._shortcmd('DELE %s' % which)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000227
Guido van Rossum484772d1998-04-06 18:27:27 +0000228
Tim Peters2344fae2001-01-15 00:50:52 +0000229 def noop(self):
230 """Does nothing.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000231
Tim Peters2344fae2001-01-15 00:50:52 +0000232 One supposes the response indicates the server is alive.
233 """
234 return self._shortcmd('NOOP')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000235
Guido van Rossum484772d1998-04-06 18:27:27 +0000236
Tim Peters2344fae2001-01-15 00:50:52 +0000237 def rset(self):
238 """Not sure what this does."""
239 return self._shortcmd('RSET')
Guido van Rossum484772d1998-04-06 18:27:27 +0000240
Guido van Rossum03774bb1998-04-09 13:50:55 +0000241
Tim Peters2344fae2001-01-15 00:50:52 +0000242 def quit(self):
243 """Signoff: commit changes on server, unlock mailbox, close connection."""
244 try:
245 resp = self._shortcmd('QUIT')
246 except error_proto, val:
247 resp = val
248 self.file.close()
249 self.sock.close()
250 del self.file, self.sock
251 return resp
Guido van Rossum484772d1998-04-06 18:27:27 +0000252
Tim Peters2344fae2001-01-15 00:50:52 +0000253 #__del__ = quit
Guido van Rossum484772d1998-04-06 18:27:27 +0000254
Guido van Rossum484772d1998-04-06 18:27:27 +0000255
Tim Peters2344fae2001-01-15 00:50:52 +0000256 # optional commands:
Guido van Rossum03774bb1998-04-09 13:50:55 +0000257
Tim Peters2344fae2001-01-15 00:50:52 +0000258 def rpop(self, user):
259 """Not sure what this does."""
260 return self._shortcmd('RPOP %s' % user)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000261
Guido van Rossum03774bb1998-04-09 13:50:55 +0000262
Moshe Zadkaccc2e3d2001-01-19 19:56:27 +0000263 timestamp = re.compile(r'\+OK.*(<[^>]+>)')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000264
Tim Peters2344fae2001-01-15 00:50:52 +0000265 def apop(self, user, secret):
266 """Authorisation
Guido van Rossum03774bb1998-04-09 13:50:55 +0000267
Tim Peters2344fae2001-01-15 00:50:52 +0000268 - only possible if server has supplied a timestamp in initial greeting.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000269
Tim Peters2344fae2001-01-15 00:50:52 +0000270 Args:
271 user - mailbox user;
272 secret - secret shared between client and server.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000273
Tim Peters2344fae2001-01-15 00:50:52 +0000274 NB: mailbox is locked by server from here to 'quit()'
275 """
Moshe Zadkaccc2e3d2001-01-19 19:56:27 +0000276 m = self.timestamp.match(self.welcome)
277 if not m:
Tim Peters2344fae2001-01-15 00:50:52 +0000278 raise error_proto('-ERR APOP not supported by server')
279 import md5
Moshe Zadkaccc2e3d2001-01-19 19:56:27 +0000280 digest = md5.new(m.group(1)+secret).digest()
Eric S. Raymond341f9292001-02-09 06:56:56 +0000281 digest = ''.join(map(lambda x:'%02x'%ord(x), digest))
Tim Peters2344fae2001-01-15 00:50:52 +0000282 return self._shortcmd('APOP %s %s' % (user, digest))
Guido van Rossum03774bb1998-04-09 13:50:55 +0000283
Guido van Rossum03774bb1998-04-09 13:50:55 +0000284
Tim Peters2344fae2001-01-15 00:50:52 +0000285 def top(self, which, howmuch):
286 """Retrieve message header of message number 'which'
287 and first 'howmuch' lines of message body.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000288
Tim Peters2344fae2001-01-15 00:50:52 +0000289 Result is in form ['response', ['line', ...], octets].
290 """
291 return self._longcmd('TOP %s %s' % (which, howmuch))
Guido van Rossum03774bb1998-04-09 13:50:55 +0000292
Guido van Rossum03774bb1998-04-09 13:50:55 +0000293
Tim Peters2344fae2001-01-15 00:50:52 +0000294 def uidl(self, which=None):
295 """Return message digest (unique id) list.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000296
Tim Peters2344fae2001-01-15 00:50:52 +0000297 If 'which', result contains unique id for that message
298 in the form 'response mesgnum uid', otherwise result is
299 the list ['response', ['mesgnum uid', ...], octets]
300 """
301 if which:
302 return self._shortcmd('UIDL %s' % which)
303 return self._longcmd('UIDL')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000304
Guido van Rossum03774bb1998-04-09 13:50:55 +0000305
Guido van Rossum484772d1998-04-06 18:27:27 +0000306if __name__ == "__main__":
Eric S. Raymond341f9292001-02-09 06:56:56 +0000307 import sys
308 a = POP3(sys.argv[1])
Tim Peters2344fae2001-01-15 00:50:52 +0000309 print a.getwelcome()
Eric S. Raymond341f9292001-02-09 06:56:56 +0000310 a.user(sys.argv[2])
311 a.pass_(sys.argv[3])
Tim Peters2344fae2001-01-15 00:50:52 +0000312 a.list()
313 (numMsgs, totalSize) = a.stat()
314 for i in range(1, numMsgs + 1):
315 (header, msg, octets) = a.retr(i)
316 print "Message ", `i`, ':'
317 for line in msg:
318 print ' ' + line
319 print '-----------------------'
320 a.quit()