blob: 5a3813cf18d68f11548ffb60719223bb8b635811 [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
Thomas Wouters7e474022000-07-16 12:04:32 +000062 authorization until QUIT, so be sure to get in, suck
Guido van Rossum03774bb1998-04-09 13:50:55 +000063 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()