blob: 97a9b0dd62aca5ba99b49689f95ce7c94ec9e33d [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]
9
Guido van Rossum484772d1998-04-06 18:27:27 +000010# Example (see the test function at the end of this file)
11
12TESTSERVER = "localhost"
13TESTACCOUNT = "test"
14TESTPASSWORD = "_passwd_"
15
16# Imports
17
Guido van Rossum03774bb1998-04-09 13:50:55 +000018import regex, socket, string
Guido van Rossum484772d1998-04-06 18:27:27 +000019
20# Exception raised when an error or invalid response is received:
Guido van Rossum03774bb1998-04-09 13:50:55 +000021
22class error_proto(Exception): pass
Guido van Rossum484772d1998-04-06 18:27:27 +000023
24# Standard Port
25POP3_PORT = 110
26
Guido van Rossum03774bb1998-04-09 13:50:55 +000027# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
28CR = '\r'
29LF = '\n'
30CRLF = CR+LF
Guido van Rossum484772d1998-04-06 18:27:27 +000031
32
33class POP3:
Guido van Rossum03774bb1998-04-09 13:50:55 +000034
35 """This class supports both the minimal and optional command sets.
36 Arguments can be strings or integers (where appropriate)
37 (e.g.: retr(1) and retr('1') both work equally well.
38
39 Minimal Command Set:
40 USER name user(name)
41 PASS string pass_(string)
42 STAT stat()
43 LIST [msg] list(msg = None)
44 RETR msg retr(msg)
45 DELE msg dele(msg)
46 NOOP noop()
47 RSET rset()
48 QUIT quit()
49
50 Optional Commands (some servers support these):
51 RPOP name rpop(name)
52 APOP name digest apop(name, digest)
53 TOP msg n top(msg, n)
54 UIDL [msg] uidl(msg = None)
55
56 Raises one exception: 'error_proto'.
57
58 Instantiate with:
59 POP3(hostname, port=110)
60
61 NB: the POP protocol locks the mailbox from user
62 authorisation until QUIT, so be sure to get in, suck
63 the messages, and quit, each time you access the
64 mailbox.
65
66 POP is a line-based protocol, which means large mail
67 messages consume lots of python cycles reading them
68 line-by-line.
69
70 If it's available on your mail server, use IMAP4
71 instead, it doesn't suffer from the two problems
72 above.
73 """
74
75
Guido van Rossum484772d1998-04-06 18:27:27 +000076 def __init__(self, host, port = POP3_PORT):
77 self.host = host
78 self.port = port
79 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum93a7c0f2000-03-28 21:45:46 +000080 self.sock.connect((self.host, self.port))
Guido van Rossum484772d1998-04-06 18:27:27 +000081 self.file = self.sock.makefile('rb')
82 self._debugging = 0
83 self.welcome = self._getresp()
84
Guido van Rossum03774bb1998-04-09 13:50:55 +000085
Guido van Rossum484772d1998-04-06 18:27:27 +000086 def _putline(self, line):
Guido van Rossum03774bb1998-04-09 13:50:55 +000087 #if self._debugging > 1: print '*put*', `line`
88 self.sock.send('%s%s' % (line, CRLF))
89
Guido van Rossum484772d1998-04-06 18:27:27 +000090
91 # Internal: send one command to the server (through _putline())
Guido van Rossum03774bb1998-04-09 13:50:55 +000092
Guido van Rossum484772d1998-04-06 18:27:27 +000093 def _putcmd(self, line):
Guido van Rossum03774bb1998-04-09 13:50:55 +000094 #if self._debugging: print '*cmd*', `line`
Guido van Rossum484772d1998-04-06 18:27:27 +000095 self._putline(line)
96
Guido van Rossum03774bb1998-04-09 13:50:55 +000097
Guido van Rossum484772d1998-04-06 18:27:27 +000098 # Internal: return one line from the server, stripping CRLF.
Guido van Rossum03774bb1998-04-09 13:50:55 +000099 # This is where all the CPU time of this module is consumed.
100 # Raise error_proto('-ERR EOF') if the connection is closed.
101
Guido van Rossum484772d1998-04-06 18:27:27 +0000102 def _getline(self):
103 line = self.file.readline()
Guido van Rossum03774bb1998-04-09 13:50:55 +0000104 #if self._debugging > 1: print '*get*', `line`
105 if not line: raise error_proto('-ERR EOF')
106 octets = len(line)
107 # server can send any combination of CR & LF
108 # however, 'readline()' returns lines ending in LF
109 # so only possibilities are ...LF, ...CRLF, CR...LF
110 if line[-2:] == CRLF:
111 return line[:-2], octets
112 if line[0] == CR:
113 return line[1:-1], octets
114 return line[:-1], octets
115
Guido van Rossum484772d1998-04-06 18:27:27 +0000116
117 # Internal: get a response from the server.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000118 # Raise 'error_proto' if the response doesn't start with '+'.
119
Guido van Rossum484772d1998-04-06 18:27:27 +0000120 def _getresp(self):
Guido van Rossum03774bb1998-04-09 13:50:55 +0000121 resp, o = self._getline()
122 #if self._debugging > 1: print '*resp*', `resp`
Guido van Rossum484772d1998-04-06 18:27:27 +0000123 c = resp[:1]
124 if c != '+':
Guido van Rossum03774bb1998-04-09 13:50:55 +0000125 raise error_proto(resp)
Guido van Rossum484772d1998-04-06 18:27:27 +0000126 return resp
127
Guido van Rossum03774bb1998-04-09 13:50:55 +0000128
Guido van Rossum484772d1998-04-06 18:27:27 +0000129 # Internal: get a response plus following text from the server.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000130
Guido van Rossum484772d1998-04-06 18:27:27 +0000131 def _getlongresp(self):
132 resp = self._getresp()
Guido van Rossum03774bb1998-04-09 13:50:55 +0000133 list = []; octets = 0
134 line, o = self._getline()
135 while line != '.':
Guido van Rossum2a91cd42000-05-09 10:56:00 +0000136 if line[:2] == '..':
137 o = o-1
138 line = line[1:]
Guido van Rossum03774bb1998-04-09 13:50:55 +0000139 octets = octets + o
Guido van Rossum484772d1998-04-06 18:27:27 +0000140 list.append(line)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000141 line, o = self._getline()
142 return resp, list, octets
143
Guido van Rossum484772d1998-04-06 18:27:27 +0000144
145 # Internal: send a command and get the response
Guido van Rossum03774bb1998-04-09 13:50:55 +0000146
Guido van Rossum484772d1998-04-06 18:27:27 +0000147 def _shortcmd(self, line):
148 self._putcmd(line)
149 return self._getresp()
150
Guido van Rossum03774bb1998-04-09 13:50:55 +0000151
Guido van Rossum484772d1998-04-06 18:27:27 +0000152 # Internal: send a command and get the response plus following text
Guido van Rossum03774bb1998-04-09 13:50:55 +0000153
Guido van Rossum484772d1998-04-06 18:27:27 +0000154 def _longcmd(self, line):
155 self._putcmd(line)
156 return self._getlongresp()
157
Guido van Rossum03774bb1998-04-09 13:50:55 +0000158
Guido van Rossum484772d1998-04-06 18:27:27 +0000159 # These can be useful:
160
Guido van Rossum03774bb1998-04-09 13:50:55 +0000161 def getwelcome(self):
Guido van Rossum484772d1998-04-06 18:27:27 +0000162 return self.welcome
163
Guido van Rossum03774bb1998-04-09 13:50:55 +0000164
Guido van Rossum484772d1998-04-06 18:27:27 +0000165 def set_debuglevel(self, level):
166 self._debugging = level
167
Guido van Rossum03774bb1998-04-09 13:50:55 +0000168
Guido van Rossum484772d1998-04-06 18:27:27 +0000169 # Here are all the POP commands:
170
171 def user(self, user):
Guido van Rossum03774bb1998-04-09 13:50:55 +0000172 """Send user name, return response
173
174 (should indicate password required).
175 """
176 return self._shortcmd('USER %s' % user)
177
Guido van Rossum484772d1998-04-06 18:27:27 +0000178
179 def pass_(self, pswd):
Guido van Rossum03774bb1998-04-09 13:50:55 +0000180 """Send password, return response
181
182 (response includes message count, mailbox size).
183
184 NB: mailbox is locked by server from here to 'quit()'
185 """
186 return self._shortcmd('PASS %s' % pswd)
187
Guido van Rossum484772d1998-04-06 18:27:27 +0000188
189 def stat(self):
Guido van Rossum03774bb1998-04-09 13:50:55 +0000190 """Get mailbox status.
191
192 Result is tuple of 2 ints (message count, mailbox size)
193 """
Guido van Rossum484772d1998-04-06 18:27:27 +0000194 retval = self._shortcmd('STAT')
195 rets = string.split(retval)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000196 #if self._debugging: print '*stat*', `rets`
Guido van Rossum484772d1998-04-06 18:27:27 +0000197 numMessages = string.atoi(rets[1])
198 sizeMessages = string.atoi(rets[2])
199 return (numMessages, sizeMessages)
200
Guido van Rossum03774bb1998-04-09 13:50:55 +0000201
Guido van Rossum8d5bef71998-09-14 17:36:51 +0000202 def list(self, which=None):
Guido van Rossum03774bb1998-04-09 13:50:55 +0000203 """Request listing, return result.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000204
Guido van Rossum8d5bef71998-09-14 17:36:51 +0000205 Result without a message number argument is in form
Guido van Rossumf6ae7431998-09-02 14:42:02 +0000206 ['response', ['mesg_num octets', ...]].
207
Guido van Rossum8d5bef71998-09-14 17:36:51 +0000208 Result when a message number argument is given is a
209 single response: the "scan listing" for that message.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000210 """
211 if which:
Guido van Rossumf6ae7431998-09-02 14:42:02 +0000212 return self._shortcmd('LIST %s' % which)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000213 return self._longcmd('LIST')
214
Guido van Rossum484772d1998-04-06 18:27:27 +0000215
216 def retr(self, which):
Guido van Rossum03774bb1998-04-09 13:50:55 +0000217 """Retrieve whole message number 'which'.
218
219 Result is in form ['response', ['line', ...], octets].
220 """
221 return self._longcmd('RETR %s' % which)
222
Guido van Rossum484772d1998-04-06 18:27:27 +0000223
224 def dele(self, which):
Guido van Rossum03774bb1998-04-09 13:50:55 +0000225 """Delete message number 'which'.
226
227 Result is 'response'.
228 """
229 return self._shortcmd('DELE %s' % which)
230
Guido van Rossum484772d1998-04-06 18:27:27 +0000231
232 def noop(self):
Guido van Rossum03774bb1998-04-09 13:50:55 +0000233 """Does nothing.
234
235 One supposes the response indicates the server is alive.
236 """
Guido van Rossum484772d1998-04-06 18:27:27 +0000237 return self._shortcmd('NOOP')
238
Guido van Rossum03774bb1998-04-09 13:50:55 +0000239
Guido van Rossum484772d1998-04-06 18:27:27 +0000240 def rset(self):
Guido van Rossum03774bb1998-04-09 13:50:55 +0000241 """Not sure what this does."""
Guido van Rossum484772d1998-04-06 18:27:27 +0000242 return self._shortcmd('RSET')
243
Guido van Rossum484772d1998-04-06 18:27:27 +0000244
245 def quit(self):
Guido van Rossum03774bb1998-04-09 13:50:55 +0000246 """Signoff: commit changes on server, unlock mailbox, close connection."""
247 try:
248 resp = self._shortcmd('QUIT')
Guido van Rossumde23cb01998-08-06 02:59:07 +0000249 except error_proto, val:
Guido van Rossum03774bb1998-04-09 13:50:55 +0000250 resp = val
Guido van Rossum484772d1998-04-06 18:27:27 +0000251 self.file.close()
252 self.sock.close()
253 del self.file, self.sock
254 return resp
255
Guido van Rossum03774bb1998-04-09 13:50:55 +0000256 #__del__ = quit
257
258
259 # optional commands:
260
261 def rpop(self, user):
262 """Not sure what this does."""
263 return self._shortcmd('RPOP %s' % user)
264
265
266 timestamp = regex.compile('\+OK.*\(<[^>]+>\)')
267
268 def apop(self, user, secret):
269 """Authorisation
270
271 - only possible if server has supplied a timestamp in initial greeting.
272
273 Args:
274 user - mailbox user;
275 secret - secret shared between client and server.
276
277 NB: mailbox is locked by server from here to 'quit()'
278 """
279 if self.timestamp.match(self.welcome) <= 0:
280 raise error_proto('-ERR APOP not supported by server')
281 import md5
282 digest = md5.new(self.timestamp.group(1)+secret).digest()
283 digest = string.join(map(lambda x:'%02x'%ord(x), digest), '')
284 return self._shortcmd('APOP %s %s' % (user, digest))
285
286
287 def top(self, which, howmuch):
288 """Retrieve message header of message number 'which'
289 and first 'howmuch' lines of message body.
290
291 Result is in form ['response', ['line', ...], octets].
292 """
293 return self._longcmd('TOP %s %s' % (which, howmuch))
294
295
296 def uidl(self, which=None):
297 """Return message digest (unique id) list.
298
Fred Drake361c0481999-05-13 18:47:25 +0000299 If 'which', result contains unique id for that message
300 in the form 'response mesgnum uid', otherwise result is
301 the list ['response', ['mesgnum uid', ...], octets]
Guido van Rossum03774bb1998-04-09 13:50:55 +0000302 """
303 if which:
304 return self._shortcmd('UIDL %s' % which)
305 return self._longcmd('UIDL')
306
307
Guido van Rossum484772d1998-04-06 18:27:27 +0000308if __name__ == "__main__":
309 a = POP3(TESTSERVER)
310 print a.getwelcome()
311 a.user(TESTACCOUNT)
312 a.pass_(TESTPASSWORD)
313 a.list()
314 (numMsgs, totalSize) = a.stat()
315 for i in range(1, numMsgs + 1):
316 (header, msg, octets) = a.retr(i)
317 print "Message ", `i`, ':'
318 for line in msg:
319 print ' ' + line
320 print '-----------------------'
321 a.quit()