blob: 2f55f7421518daf4714038eefe284a0d29b00df5 [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
Tim Peters2344fae2001-01-15 00:50:52 +000035 """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.
Guido van Rossum03774bb1998-04-09 13:50:55 +000038
Tim Peters2344fae2001-01-15 00:50:52 +000039 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()
Guido van Rossum03774bb1998-04-09 13:50:55 +000049
Tim Peters2344fae2001-01-15 00:50:52 +000050 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)
Guido van Rossum03774bb1998-04-09 13:50:55 +000055
Tim Peters2344fae2001-01-15 00:50:52 +000056 Raises one exception: 'error_proto'.
Guido van Rossum03774bb1998-04-09 13:50:55 +000057
Tim Peters2344fae2001-01-15 00:50:52 +000058 Instantiate with:
59 POP3(hostname, port=110)
Guido van Rossum03774bb1998-04-09 13:50:55 +000060
Tim Peters2344fae2001-01-15 00:50:52 +000061 NB: the POP protocol locks the mailbox from user
62 authorization until QUIT, so be sure to get in, suck
63 the messages, and quit, each time you access the
64 mailbox.
Guido van Rossum03774bb1998-04-09 13:50:55 +000065
Tim Peters2344fae2001-01-15 00:50:52 +000066 POP is a line-based protocol, which means large mail
67 messages consume lots of python cycles reading them
68 line-by-line.
Guido van Rossum03774bb1998-04-09 13:50:55 +000069
Tim Peters2344fae2001-01-15 00:50:52 +000070 If it's available on your mail server, use IMAP4
71 instead, it doesn't suffer from the two problems
72 above.
73 """
Guido van Rossum03774bb1998-04-09 13:50:55 +000074
75
Tim Peters2344fae2001-01-15 00:50:52 +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)
80 self.sock.connect((self.host, self.port))
81 self.file = self.sock.makefile('rb')
82 self._debugging = 0
83 self.welcome = self._getresp()
Guido van Rossum484772d1998-04-06 18:27:27 +000084
Guido van Rossum03774bb1998-04-09 13:50:55 +000085
Tim Peters2344fae2001-01-15 00:50:52 +000086 def _putline(self, line):
87 #if self._debugging > 1: print '*put*', `line`
88 self.sock.send('%s%s' % (line, CRLF))
Guido van Rossum03774bb1998-04-09 13:50:55 +000089
Guido van Rossum484772d1998-04-06 18:27:27 +000090
Tim Peters2344fae2001-01-15 00:50:52 +000091 # Internal: send one command to the server (through _putline())
Guido van Rossum03774bb1998-04-09 13:50:55 +000092
Tim Peters2344fae2001-01-15 00:50:52 +000093 def _putcmd(self, line):
94 #if self._debugging: print '*cmd*', `line`
95 self._putline(line)
Guido van Rossum484772d1998-04-06 18:27:27 +000096
Guido van Rossum03774bb1998-04-09 13:50:55 +000097
Tim Peters2344fae2001-01-15 00:50:52 +000098 # Internal: return one line from the server, stripping CRLF.
99 # This is where all the CPU time of this module is consumed.
100 # Raise error_proto('-ERR EOF') if the connection is closed.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000101
Tim Peters2344fae2001-01-15 00:50:52 +0000102 def _getline(self):
103 line = self.file.readline()
104 #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
Guido van Rossum03774bb1998-04-09 13:50:55 +0000115
Guido van Rossum484772d1998-04-06 18:27:27 +0000116
Tim Peters2344fae2001-01-15 00:50:52 +0000117 # Internal: get a response from the server.
118 # Raise 'error_proto' if the response doesn't start with '+'.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000119
Tim Peters2344fae2001-01-15 00:50:52 +0000120 def _getresp(self):
121 resp, o = self._getline()
122 #if self._debugging > 1: print '*resp*', `resp`
123 c = resp[:1]
124 if c != '+':
125 raise error_proto(resp)
126 return resp
Guido van Rossum484772d1998-04-06 18:27:27 +0000127
Guido van Rossum03774bb1998-04-09 13:50:55 +0000128
Tim Peters2344fae2001-01-15 00:50:52 +0000129 # Internal: get a response plus following text from the server.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000130
Tim Peters2344fae2001-01-15 00:50:52 +0000131 def _getlongresp(self):
132 resp = self._getresp()
133 list = []; octets = 0
134 line, o = self._getline()
135 while line != '.':
136 if line[:2] == '..':
137 o = o-1
138 line = line[1:]
139 octets = octets + o
140 list.append(line)
141 line, o = self._getline()
142 return resp, list, octets
Guido van Rossum03774bb1998-04-09 13:50:55 +0000143
Guido van Rossum484772d1998-04-06 18:27:27 +0000144
Tim Peters2344fae2001-01-15 00:50:52 +0000145 # Internal: send a command and get the response
Guido van Rossum03774bb1998-04-09 13:50:55 +0000146
Tim Peters2344fae2001-01-15 00:50:52 +0000147 def _shortcmd(self, line):
148 self._putcmd(line)
149 return self._getresp()
Guido van Rossum484772d1998-04-06 18:27:27 +0000150
Guido van Rossum03774bb1998-04-09 13:50:55 +0000151
Tim Peters2344fae2001-01-15 00:50:52 +0000152 # Internal: send a command and get the response plus following text
Guido van Rossum03774bb1998-04-09 13:50:55 +0000153
Tim Peters2344fae2001-01-15 00:50:52 +0000154 def _longcmd(self, line):
155 self._putcmd(line)
156 return self._getlongresp()
Guido van Rossum484772d1998-04-06 18:27:27 +0000157
Guido van Rossum03774bb1998-04-09 13:50:55 +0000158
Tim Peters2344fae2001-01-15 00:50:52 +0000159 # These can be useful:
160
161 def getwelcome(self):
162 return self.welcome
163
164
165 def set_debuglevel(self, level):
166 self._debugging = level
167
168
169 # Here are all the POP commands:
170
171 def user(self, user):
172 """Send user name, return response
Guido van Rossum484772d1998-04-06 18:27:27 +0000173
Tim Peters2344fae2001-01-15 00:50:52 +0000174 (should indicate password required).
175 """
176 return self._shortcmd('USER %s' % user)
Guido van Rossum484772d1998-04-06 18:27:27 +0000177
Guido van Rossum03774bb1998-04-09 13:50:55 +0000178
Tim Peters2344fae2001-01-15 00:50:52 +0000179 def pass_(self, pswd):
180 """Send password, return response
Guido van Rossum484772d1998-04-06 18:27:27 +0000181
Tim Peters2344fae2001-01-15 00:50:52 +0000182 (response includes message count, mailbox size).
Guido van Rossum03774bb1998-04-09 13:50:55 +0000183
Tim Peters2344fae2001-01-15 00:50:52 +0000184 NB: mailbox is locked by server from here to 'quit()'
185 """
186 return self._shortcmd('PASS %s' % pswd)
Guido van Rossum484772d1998-04-06 18:27:27 +0000187
Guido van Rossum03774bb1998-04-09 13:50:55 +0000188
Tim Peters2344fae2001-01-15 00:50:52 +0000189 def stat(self):
190 """Get mailbox status.
Guido van Rossum484772d1998-04-06 18:27:27 +0000191
Tim Peters2344fae2001-01-15 00:50:52 +0000192 Result is tuple of 2 ints (message count, mailbox size)
193 """
194 retval = self._shortcmd('STAT')
195 rets = string.split(retval)
196 #if self._debugging: print '*stat*', `rets`
197 numMessages = string.atoi(rets[1])
198 sizeMessages = string.atoi(rets[2])
199 return (numMessages, sizeMessages)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000200
Guido van Rossum03774bb1998-04-09 13:50:55 +0000201
Tim Peters2344fae2001-01-15 00:50:52 +0000202 def list(self, which=None):
203 """Request listing, return result.
Guido van Rossum484772d1998-04-06 18:27:27 +0000204
Tim Peters2344fae2001-01-15 00:50:52 +0000205 Result without a message number argument is in form
206 ['response', ['mesg_num octets', ...]].
Guido van Rossum484772d1998-04-06 18:27:27 +0000207
Tim Peters2344fae2001-01-15 00:50:52 +0000208 Result when a message number argument is given is a
209 single response: the "scan listing" for that message.
210 """
211 if which:
212 return self._shortcmd('LIST %s' % which)
213 return self._longcmd('LIST')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000214
Guido van Rossum03774bb1998-04-09 13:50:55 +0000215
Tim Peters2344fae2001-01-15 00:50:52 +0000216 def retr(self, which):
217 """Retrieve whole message number 'which'.
Guido van Rossumf6ae7431998-09-02 14:42:02 +0000218
Tim Peters2344fae2001-01-15 00:50:52 +0000219 Result is in form ['response', ['line', ...], octets].
220 """
221 return self._longcmd('RETR %s' % which)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000222
Guido van Rossum484772d1998-04-06 18:27:27 +0000223
Tim Peters2344fae2001-01-15 00:50:52 +0000224 def dele(self, which):
225 """Delete message number 'which'.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000226
Tim Peters2344fae2001-01-15 00:50:52 +0000227 Result is 'response'.
228 """
229 return self._shortcmd('DELE %s' % which)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000230
Guido van Rossum484772d1998-04-06 18:27:27 +0000231
Tim Peters2344fae2001-01-15 00:50:52 +0000232 def noop(self):
233 """Does nothing.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000234
Tim Peters2344fae2001-01-15 00:50:52 +0000235 One supposes the response indicates the server is alive.
236 """
237 return self._shortcmd('NOOP')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000238
Guido van Rossum484772d1998-04-06 18:27:27 +0000239
Tim Peters2344fae2001-01-15 00:50:52 +0000240 def rset(self):
241 """Not sure what this does."""
242 return self._shortcmd('RSET')
Guido van Rossum484772d1998-04-06 18:27:27 +0000243
Guido van Rossum03774bb1998-04-09 13:50:55 +0000244
Tim Peters2344fae2001-01-15 00:50:52 +0000245 def quit(self):
246 """Signoff: commit changes on server, unlock mailbox, close connection."""
247 try:
248 resp = self._shortcmd('QUIT')
249 except error_proto, val:
250 resp = val
251 self.file.close()
252 self.sock.close()
253 del self.file, self.sock
254 return resp
Guido van Rossum484772d1998-04-06 18:27:27 +0000255
Tim Peters2344fae2001-01-15 00:50:52 +0000256 #__del__ = quit
Guido van Rossum484772d1998-04-06 18:27:27 +0000257
Guido van Rossum484772d1998-04-06 18:27:27 +0000258
Tim Peters2344fae2001-01-15 00:50:52 +0000259 # optional commands:
Guido van Rossum03774bb1998-04-09 13:50:55 +0000260
Tim Peters2344fae2001-01-15 00:50:52 +0000261 def rpop(self, user):
262 """Not sure what this does."""
263 return self._shortcmd('RPOP %s' % user)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000264
Guido van Rossum03774bb1998-04-09 13:50:55 +0000265
Tim Peters2344fae2001-01-15 00:50:52 +0000266 timestamp = regex.compile('\+OK.*\(<[^>]+>\)')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000267
Tim Peters2344fae2001-01-15 00:50:52 +0000268 def apop(self, user, secret):
269 """Authorisation
Guido van Rossum03774bb1998-04-09 13:50:55 +0000270
Tim Peters2344fae2001-01-15 00:50:52 +0000271 - only possible if server has supplied a timestamp in initial greeting.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000272
Tim Peters2344fae2001-01-15 00:50:52 +0000273 Args:
274 user - mailbox user;
275 secret - secret shared between client and server.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000276
Tim Peters2344fae2001-01-15 00:50:52 +0000277 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))
Guido van Rossum03774bb1998-04-09 13:50:55 +0000285
Guido van Rossum03774bb1998-04-09 13:50:55 +0000286
Tim Peters2344fae2001-01-15 00:50:52 +0000287 def top(self, which, howmuch):
288 """Retrieve message header of message number 'which'
289 and first 'howmuch' lines of message body.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000290
Tim Peters2344fae2001-01-15 00:50:52 +0000291 Result is in form ['response', ['line', ...], octets].
292 """
293 return self._longcmd('TOP %s %s' % (which, howmuch))
Guido van Rossum03774bb1998-04-09 13:50:55 +0000294
Guido van Rossum03774bb1998-04-09 13:50:55 +0000295
Tim Peters2344fae2001-01-15 00:50:52 +0000296 def uidl(self, which=None):
297 """Return message digest (unique id) list.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000298
Tim Peters2344fae2001-01-15 00:50:52 +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]
302 """
303 if which:
304 return self._shortcmd('UIDL %s' % which)
305 return self._longcmd('UIDL')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000306
Guido van Rossum03774bb1998-04-09 13:50:55 +0000307
Guido van Rossum484772d1998-04-06 18:27:27 +0000308if __name__ == "__main__":
Tim Peters2344fae2001-01-15 00:50:52 +0000309 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()