blob: 98b81ce620e42b4160c1e0357f5c943a393a20a0 [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
Skip Montanaroc62c81e2001-02-12 02:00:42 +000017__all__ = ["POP3","error_proto"]
18
Guido van Rossum484772d1998-04-06 18:27:27 +000019# Exception raised when an error or invalid response is received:
Guido van Rossum03774bb1998-04-09 13:50:55 +000020
21class error_proto(Exception): pass
Guido van Rossum484772d1998-04-06 18:27:27 +000022
23# Standard Port
24POP3_PORT = 110
25
Guido van Rossum03774bb1998-04-09 13:50:55 +000026# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
27CR = '\r'
28LF = '\n'
29CRLF = CR+LF
Guido van Rossum484772d1998-04-06 18:27:27 +000030
31
32class POP3:
Guido van Rossum03774bb1998-04-09 13:50:55 +000033
Tim Peters2344fae2001-01-15 00:50:52 +000034 """This class supports both the minimal and optional command sets.
35 Arguments can be strings or integers (where appropriate)
36 (e.g.: retr(1) and retr('1') both work equally well.
Guido van Rossum03774bb1998-04-09 13:50:55 +000037
Tim Peters2344fae2001-01-15 00:50:52 +000038 Minimal Command Set:
39 USER name user(name)
40 PASS string pass_(string)
41 STAT stat()
42 LIST [msg] list(msg = None)
43 RETR msg retr(msg)
44 DELE msg dele(msg)
45 NOOP noop()
46 RSET rset()
47 QUIT quit()
Guido van Rossum03774bb1998-04-09 13:50:55 +000048
Tim Peters2344fae2001-01-15 00:50:52 +000049 Optional Commands (some servers support these):
50 RPOP name rpop(name)
51 APOP name digest apop(name, digest)
52 TOP msg n top(msg, n)
53 UIDL [msg] uidl(msg = None)
Guido van Rossum03774bb1998-04-09 13:50:55 +000054
Tim Peters2344fae2001-01-15 00:50:52 +000055 Raises one exception: 'error_proto'.
Guido van Rossum03774bb1998-04-09 13:50:55 +000056
Tim Peters2344fae2001-01-15 00:50:52 +000057 Instantiate with:
58 POP3(hostname, port=110)
Guido van Rossum03774bb1998-04-09 13:50:55 +000059
Tim Peters2344fae2001-01-15 00:50:52 +000060 NB: the POP protocol locks the mailbox from user
61 authorization until QUIT, so be sure to get in, suck
62 the messages, and quit, each time you access the
63 mailbox.
Guido van Rossum03774bb1998-04-09 13:50:55 +000064
Tim Peters2344fae2001-01-15 00:50:52 +000065 POP is a line-based protocol, which means large mail
66 messages consume lots of python cycles reading them
67 line-by-line.
Guido van Rossum03774bb1998-04-09 13:50:55 +000068
Tim Peters2344fae2001-01-15 00:50:52 +000069 If it's available on your mail server, use IMAP4
70 instead, it doesn't suffer from the two problems
71 above.
72 """
Guido van Rossum03774bb1998-04-09 13:50:55 +000073
74
Tim Peters2344fae2001-01-15 00:50:52 +000075 def __init__(self, host, port = POP3_PORT):
Martin v. Löwis4eb59402001-07-26 13:37:33 +000076 self.host = host
77 self.port = port
Martin v. Löwis2ad25692001-07-31 08:40:21 +000078 msg = "getaddrinfo returns an empty list"
Martin v. Löwis4eb59402001-07-26 13:37:33 +000079 for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
80 af, socktype, proto, canonname, sa = res
81 try:
82 self.sock = socket.socket(af, socktype, proto)
83 self.sock.connect(sa)
84 except socket.error, msg:
85 self.sock.close()
86 self.sock = None
87 continue
88 break
89 if not self.sock:
90 raise socket.error, msg
91 self.file = self.sock.makefile('rb')
92 self._debugging = 0
93 self.welcome = self._getresp()
Guido van Rossum484772d1998-04-06 18:27:27 +000094
Guido van Rossum03774bb1998-04-09 13:50:55 +000095
Tim Peters2344fae2001-01-15 00:50:52 +000096 def _putline(self, line):
97 #if self._debugging > 1: print '*put*', `line`
98 self.sock.send('%s%s' % (line, CRLF))
Guido van Rossum03774bb1998-04-09 13:50:55 +000099
Guido van Rossum484772d1998-04-06 18:27:27 +0000100
Tim Peters2344fae2001-01-15 00:50:52 +0000101 # Internal: send one command to the server (through _putline())
Guido van Rossum03774bb1998-04-09 13:50:55 +0000102
Tim Peters2344fae2001-01-15 00:50:52 +0000103 def _putcmd(self, line):
104 #if self._debugging: print '*cmd*', `line`
105 self._putline(line)
Guido van Rossum484772d1998-04-06 18:27:27 +0000106
Guido van Rossum03774bb1998-04-09 13:50:55 +0000107
Tim Peters2344fae2001-01-15 00:50:52 +0000108 # Internal: return one line from the server, stripping CRLF.
109 # This is where all the CPU time of this module is consumed.
110 # Raise error_proto('-ERR EOF') if the connection is closed.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000111
Tim Peters2344fae2001-01-15 00:50:52 +0000112 def _getline(self):
113 line = self.file.readline()
114 #if self._debugging > 1: print '*get*', `line`
115 if not line: raise error_proto('-ERR EOF')
116 octets = len(line)
117 # server can send any combination of CR & LF
118 # however, 'readline()' returns lines ending in LF
119 # so only possibilities are ...LF, ...CRLF, CR...LF
120 if line[-2:] == CRLF:
121 return line[:-2], octets
122 if line[0] == CR:
123 return line[1:-1], octets
124 return line[:-1], octets
Guido van Rossum03774bb1998-04-09 13:50:55 +0000125
Guido van Rossum484772d1998-04-06 18:27:27 +0000126
Tim Peters2344fae2001-01-15 00:50:52 +0000127 # Internal: get a response from the server.
128 # Raise 'error_proto' if the response doesn't start with '+'.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000129
Tim Peters2344fae2001-01-15 00:50:52 +0000130 def _getresp(self):
131 resp, o = self._getline()
132 #if self._debugging > 1: print '*resp*', `resp`
133 c = resp[:1]
134 if c != '+':
135 raise error_proto(resp)
136 return resp
Guido van Rossum484772d1998-04-06 18:27:27 +0000137
Guido van Rossum03774bb1998-04-09 13:50:55 +0000138
Tim Peters2344fae2001-01-15 00:50:52 +0000139 # Internal: get a response plus following text from the server.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000140
Tim Peters2344fae2001-01-15 00:50:52 +0000141 def _getlongresp(self):
142 resp = self._getresp()
143 list = []; octets = 0
144 line, o = self._getline()
145 while line != '.':
146 if line[:2] == '..':
147 o = o-1
148 line = line[1:]
149 octets = octets + o
150 list.append(line)
151 line, o = self._getline()
152 return resp, list, octets
Guido van Rossum03774bb1998-04-09 13:50:55 +0000153
Guido van Rossum484772d1998-04-06 18:27:27 +0000154
Tim Peters2344fae2001-01-15 00:50:52 +0000155 # Internal: send a command and get the response
Guido van Rossum03774bb1998-04-09 13:50:55 +0000156
Tim Peters2344fae2001-01-15 00:50:52 +0000157 def _shortcmd(self, line):
158 self._putcmd(line)
159 return self._getresp()
Guido van Rossum484772d1998-04-06 18:27:27 +0000160
Guido van Rossum03774bb1998-04-09 13:50:55 +0000161
Tim Peters2344fae2001-01-15 00:50:52 +0000162 # Internal: send a command and get the response plus following text
Guido van Rossum03774bb1998-04-09 13:50:55 +0000163
Tim Peters2344fae2001-01-15 00:50:52 +0000164 def _longcmd(self, line):
165 self._putcmd(line)
166 return self._getlongresp()
Guido van Rossum484772d1998-04-06 18:27:27 +0000167
Guido van Rossum03774bb1998-04-09 13:50:55 +0000168
Tim Peters2344fae2001-01-15 00:50:52 +0000169 # These can be useful:
170
171 def getwelcome(self):
172 return self.welcome
173
174
175 def set_debuglevel(self, level):
176 self._debugging = level
177
178
179 # Here are all the POP commands:
180
181 def user(self, user):
182 """Send user name, return response
Guido van Rossum484772d1998-04-06 18:27:27 +0000183
Tim Peters2344fae2001-01-15 00:50:52 +0000184 (should indicate password required).
185 """
186 return self._shortcmd('USER %s' % user)
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 pass_(self, pswd):
190 """Send password, return response
Guido van Rossum484772d1998-04-06 18:27:27 +0000191
Tim Peters2344fae2001-01-15 00:50:52 +0000192 (response includes message count, mailbox size).
Guido van Rossum03774bb1998-04-09 13:50:55 +0000193
Tim Peters2344fae2001-01-15 00:50:52 +0000194 NB: mailbox is locked by server from here to 'quit()'
195 """
196 return self._shortcmd('PASS %s' % pswd)
Guido van Rossum484772d1998-04-06 18:27:27 +0000197
Guido van Rossum03774bb1998-04-09 13:50:55 +0000198
Tim Peters2344fae2001-01-15 00:50:52 +0000199 def stat(self):
200 """Get mailbox status.
Guido van Rossum484772d1998-04-06 18:27:27 +0000201
Tim Peters2344fae2001-01-15 00:50:52 +0000202 Result is tuple of 2 ints (message count, mailbox size)
203 """
204 retval = self._shortcmd('STAT')
Eric S. Raymond341f9292001-02-09 06:56:56 +0000205 rets = retval.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000206 #if self._debugging: print '*stat*', `rets`
Eric S. Raymond341f9292001-02-09 06:56:56 +0000207 numMessages = int(rets[1])
208 sizeMessages = int(rets[2])
Tim Peters2344fae2001-01-15 00:50:52 +0000209 return (numMessages, sizeMessages)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000210
Guido van Rossum03774bb1998-04-09 13:50:55 +0000211
Tim Peters2344fae2001-01-15 00:50:52 +0000212 def list(self, which=None):
213 """Request listing, return result.
Guido van Rossum484772d1998-04-06 18:27:27 +0000214
Tim Peters2344fae2001-01-15 00:50:52 +0000215 Result without a message number argument is in form
216 ['response', ['mesg_num octets', ...]].
Guido van Rossum484772d1998-04-06 18:27:27 +0000217
Tim Peters2344fae2001-01-15 00:50:52 +0000218 Result when a message number argument is given is a
219 single response: the "scan listing" for that message.
220 """
221 if which:
222 return self._shortcmd('LIST %s' % which)
223 return self._longcmd('LIST')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000224
Guido van Rossum03774bb1998-04-09 13:50:55 +0000225
Tim Peters2344fae2001-01-15 00:50:52 +0000226 def retr(self, which):
227 """Retrieve whole message number 'which'.
Guido van Rossumf6ae7431998-09-02 14:42:02 +0000228
Tim Peters2344fae2001-01-15 00:50:52 +0000229 Result is in form ['response', ['line', ...], octets].
230 """
231 return self._longcmd('RETR %s' % which)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000232
Guido van Rossum484772d1998-04-06 18:27:27 +0000233
Tim Peters2344fae2001-01-15 00:50:52 +0000234 def dele(self, which):
235 """Delete message number 'which'.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000236
Tim Peters2344fae2001-01-15 00:50:52 +0000237 Result is 'response'.
238 """
239 return self._shortcmd('DELE %s' % which)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000240
Guido van Rossum484772d1998-04-06 18:27:27 +0000241
Tim Peters2344fae2001-01-15 00:50:52 +0000242 def noop(self):
243 """Does nothing.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000244
Tim Peters2344fae2001-01-15 00:50:52 +0000245 One supposes the response indicates the server is alive.
246 """
247 return self._shortcmd('NOOP')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000248
Guido van Rossum484772d1998-04-06 18:27:27 +0000249
Tim Peters2344fae2001-01-15 00:50:52 +0000250 def rset(self):
251 """Not sure what this does."""
252 return self._shortcmd('RSET')
Guido van Rossum484772d1998-04-06 18:27:27 +0000253
Guido van Rossum03774bb1998-04-09 13:50:55 +0000254
Tim Peters2344fae2001-01-15 00:50:52 +0000255 def quit(self):
256 """Signoff: commit changes on server, unlock mailbox, close connection."""
257 try:
258 resp = self._shortcmd('QUIT')
259 except error_proto, val:
260 resp = val
261 self.file.close()
262 self.sock.close()
263 del self.file, self.sock
264 return resp
Guido van Rossum484772d1998-04-06 18:27:27 +0000265
Tim Peters2344fae2001-01-15 00:50:52 +0000266 #__del__ = quit
Guido van Rossum484772d1998-04-06 18:27:27 +0000267
Guido van Rossum484772d1998-04-06 18:27:27 +0000268
Tim Peters2344fae2001-01-15 00:50:52 +0000269 # optional commands:
Guido van Rossum03774bb1998-04-09 13:50:55 +0000270
Tim Peters2344fae2001-01-15 00:50:52 +0000271 def rpop(self, user):
272 """Not sure what this does."""
273 return self._shortcmd('RPOP %s' % user)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000274
Guido van Rossum03774bb1998-04-09 13:50:55 +0000275
Moshe Zadkaccc2e3d2001-01-19 19:56:27 +0000276 timestamp = re.compile(r'\+OK.*(<[^>]+>)')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000277
Tim Peters2344fae2001-01-15 00:50:52 +0000278 def apop(self, user, secret):
279 """Authorisation
Guido van Rossum03774bb1998-04-09 13:50:55 +0000280
Tim Peters2344fae2001-01-15 00:50:52 +0000281 - only possible if server has supplied a timestamp in initial greeting.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000282
Tim Peters2344fae2001-01-15 00:50:52 +0000283 Args:
284 user - mailbox user;
285 secret - secret shared between client and server.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000286
Tim Peters2344fae2001-01-15 00:50:52 +0000287 NB: mailbox is locked by server from here to 'quit()'
288 """
Moshe Zadkaccc2e3d2001-01-19 19:56:27 +0000289 m = self.timestamp.match(self.welcome)
290 if not m:
Tim Peters2344fae2001-01-15 00:50:52 +0000291 raise error_proto('-ERR APOP not supported by server')
292 import md5
Moshe Zadkaccc2e3d2001-01-19 19:56:27 +0000293 digest = md5.new(m.group(1)+secret).digest()
Eric S. Raymond341f9292001-02-09 06:56:56 +0000294 digest = ''.join(map(lambda x:'%02x'%ord(x), digest))
Tim Peters2344fae2001-01-15 00:50:52 +0000295 return self._shortcmd('APOP %s %s' % (user, digest))
Guido van Rossum03774bb1998-04-09 13:50:55 +0000296
Guido van Rossum03774bb1998-04-09 13:50:55 +0000297
Tim Peters2344fae2001-01-15 00:50:52 +0000298 def top(self, which, howmuch):
299 """Retrieve message header of message number 'which'
300 and first 'howmuch' lines of message body.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000301
Tim Peters2344fae2001-01-15 00:50:52 +0000302 Result is in form ['response', ['line', ...], octets].
303 """
304 return self._longcmd('TOP %s %s' % (which, howmuch))
Guido van Rossum03774bb1998-04-09 13:50:55 +0000305
Guido van Rossum03774bb1998-04-09 13:50:55 +0000306
Tim Peters2344fae2001-01-15 00:50:52 +0000307 def uidl(self, which=None):
308 """Return message digest (unique id) list.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000309
Tim Peters2344fae2001-01-15 00:50:52 +0000310 If 'which', result contains unique id for that message
311 in the form 'response mesgnum uid', otherwise result is
312 the list ['response', ['mesgnum uid', ...], octets]
313 """
314 if which:
315 return self._shortcmd('UIDL %s' % which)
316 return self._longcmd('UIDL')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000317
Guido van Rossum03774bb1998-04-09 13:50:55 +0000318
Guido van Rossum484772d1998-04-06 18:27:27 +0000319if __name__ == "__main__":
Eric S. Raymond341f9292001-02-09 06:56:56 +0000320 import sys
321 a = POP3(sys.argv[1])
Tim Peters2344fae2001-01-15 00:50:52 +0000322 print a.getwelcome()
Eric S. Raymond341f9292001-02-09 06:56:56 +0000323 a.user(sys.argv[2])
324 a.pass_(sys.argv[3])
Tim Peters2344fae2001-01-15 00:50:52 +0000325 a.list()
326 (numMsgs, totalSize) = a.stat()
327 for i in range(1, numMsgs + 1):
328 (header, msg, octets) = a.retr(i)
329 print "Message ", `i`, ':'
330 for line in msg:
331 print ' ' + line
332 print '-----------------------'
333 a.quit()