blob: 592f7dfa90559b8f28d468232c7bef0bb64dc320 [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
78 for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
79 af, socktype, proto, canonname, sa = res
80 try:
81 self.sock = socket.socket(af, socktype, proto)
82 self.sock.connect(sa)
83 except socket.error, msg:
84 self.sock.close()
85 self.sock = None
86 continue
87 break
88 if not self.sock:
89 raise socket.error, msg
90 self.file = self.sock.makefile('rb')
91 self._debugging = 0
92 self.welcome = self._getresp()
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 def _putline(self, line):
96 #if self._debugging > 1: print '*put*', `line`
97 self.sock.send('%s%s' % (line, CRLF))
Guido van Rossum03774bb1998-04-09 13:50:55 +000098
Guido van Rossum484772d1998-04-06 18:27:27 +000099
Tim Peters2344fae2001-01-15 00:50:52 +0000100 # Internal: send one command to the server (through _putline())
Guido van Rossum03774bb1998-04-09 13:50:55 +0000101
Tim Peters2344fae2001-01-15 00:50:52 +0000102 def _putcmd(self, line):
103 #if self._debugging: print '*cmd*', `line`
104 self._putline(line)
Guido van Rossum484772d1998-04-06 18:27:27 +0000105
Guido van Rossum03774bb1998-04-09 13:50:55 +0000106
Tim Peters2344fae2001-01-15 00:50:52 +0000107 # Internal: return one line from the server, stripping CRLF.
108 # This is where all the CPU time of this module is consumed.
109 # Raise error_proto('-ERR EOF') if the connection is closed.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000110
Tim Peters2344fae2001-01-15 00:50:52 +0000111 def _getline(self):
112 line = self.file.readline()
113 #if self._debugging > 1: print '*get*', `line`
114 if not line: raise error_proto('-ERR EOF')
115 octets = len(line)
116 # server can send any combination of CR & LF
117 # however, 'readline()' returns lines ending in LF
118 # so only possibilities are ...LF, ...CRLF, CR...LF
119 if line[-2:] == CRLF:
120 return line[:-2], octets
121 if line[0] == CR:
122 return line[1:-1], octets
123 return line[:-1], octets
Guido van Rossum03774bb1998-04-09 13:50:55 +0000124
Guido van Rossum484772d1998-04-06 18:27:27 +0000125
Tim Peters2344fae2001-01-15 00:50:52 +0000126 # Internal: get a response from the server.
127 # Raise 'error_proto' if the response doesn't start with '+'.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000128
Tim Peters2344fae2001-01-15 00:50:52 +0000129 def _getresp(self):
130 resp, o = self._getline()
131 #if self._debugging > 1: print '*resp*', `resp`
132 c = resp[:1]
133 if c != '+':
134 raise error_proto(resp)
135 return resp
Guido van Rossum484772d1998-04-06 18:27:27 +0000136
Guido van Rossum03774bb1998-04-09 13:50:55 +0000137
Tim Peters2344fae2001-01-15 00:50:52 +0000138 # Internal: get a response plus following text from the server.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000139
Tim Peters2344fae2001-01-15 00:50:52 +0000140 def _getlongresp(self):
141 resp = self._getresp()
142 list = []; octets = 0
143 line, o = self._getline()
144 while line != '.':
145 if line[:2] == '..':
146 o = o-1
147 line = line[1:]
148 octets = octets + o
149 list.append(line)
150 line, o = self._getline()
151 return resp, list, octets
Guido van Rossum03774bb1998-04-09 13:50:55 +0000152
Guido van Rossum484772d1998-04-06 18:27:27 +0000153
Tim Peters2344fae2001-01-15 00:50:52 +0000154 # Internal: send a command and get the response
Guido van Rossum03774bb1998-04-09 13:50:55 +0000155
Tim Peters2344fae2001-01-15 00:50:52 +0000156 def _shortcmd(self, line):
157 self._putcmd(line)
158 return self._getresp()
Guido van Rossum484772d1998-04-06 18:27:27 +0000159
Guido van Rossum03774bb1998-04-09 13:50:55 +0000160
Tim Peters2344fae2001-01-15 00:50:52 +0000161 # Internal: send a command and get the response plus following text
Guido van Rossum03774bb1998-04-09 13:50:55 +0000162
Tim Peters2344fae2001-01-15 00:50:52 +0000163 def _longcmd(self, line):
164 self._putcmd(line)
165 return self._getlongresp()
Guido van Rossum484772d1998-04-06 18:27:27 +0000166
Guido van Rossum03774bb1998-04-09 13:50:55 +0000167
Tim Peters2344fae2001-01-15 00:50:52 +0000168 # These can be useful:
169
170 def getwelcome(self):
171 return self.welcome
172
173
174 def set_debuglevel(self, level):
175 self._debugging = level
176
177
178 # Here are all the POP commands:
179
180 def user(self, user):
181 """Send user name, return response
Guido van Rossum484772d1998-04-06 18:27:27 +0000182
Tim Peters2344fae2001-01-15 00:50:52 +0000183 (should indicate password required).
184 """
185 return self._shortcmd('USER %s' % user)
Guido van Rossum484772d1998-04-06 18:27:27 +0000186
Guido van Rossum03774bb1998-04-09 13:50:55 +0000187
Tim Peters2344fae2001-01-15 00:50:52 +0000188 def pass_(self, pswd):
189 """Send password, return response
Guido van Rossum484772d1998-04-06 18:27:27 +0000190
Tim Peters2344fae2001-01-15 00:50:52 +0000191 (response includes message count, mailbox size).
Guido van Rossum03774bb1998-04-09 13:50:55 +0000192
Tim Peters2344fae2001-01-15 00:50:52 +0000193 NB: mailbox is locked by server from here to 'quit()'
194 """
195 return self._shortcmd('PASS %s' % pswd)
Guido van Rossum484772d1998-04-06 18:27:27 +0000196
Guido van Rossum03774bb1998-04-09 13:50:55 +0000197
Tim Peters2344fae2001-01-15 00:50:52 +0000198 def stat(self):
199 """Get mailbox status.
Guido van Rossum484772d1998-04-06 18:27:27 +0000200
Tim Peters2344fae2001-01-15 00:50:52 +0000201 Result is tuple of 2 ints (message count, mailbox size)
202 """
203 retval = self._shortcmd('STAT')
Eric S. Raymond341f9292001-02-09 06:56:56 +0000204 rets = retval.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000205 #if self._debugging: print '*stat*', `rets`
Eric S. Raymond341f9292001-02-09 06:56:56 +0000206 numMessages = int(rets[1])
207 sizeMessages = int(rets[2])
Tim Peters2344fae2001-01-15 00:50:52 +0000208 return (numMessages, sizeMessages)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000209
Guido van Rossum03774bb1998-04-09 13:50:55 +0000210
Tim Peters2344fae2001-01-15 00:50:52 +0000211 def list(self, which=None):
212 """Request listing, return result.
Guido van Rossum484772d1998-04-06 18:27:27 +0000213
Tim Peters2344fae2001-01-15 00:50:52 +0000214 Result without a message number argument is in form
215 ['response', ['mesg_num octets', ...]].
Guido van Rossum484772d1998-04-06 18:27:27 +0000216
Tim Peters2344fae2001-01-15 00:50:52 +0000217 Result when a message number argument is given is a
218 single response: the "scan listing" for that message.
219 """
220 if which:
221 return self._shortcmd('LIST %s' % which)
222 return self._longcmd('LIST')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000223
Guido van Rossum03774bb1998-04-09 13:50:55 +0000224
Tim Peters2344fae2001-01-15 00:50:52 +0000225 def retr(self, which):
226 """Retrieve whole message number 'which'.
Guido van Rossumf6ae7431998-09-02 14:42:02 +0000227
Tim Peters2344fae2001-01-15 00:50:52 +0000228 Result is in form ['response', ['line', ...], octets].
229 """
230 return self._longcmd('RETR %s' % which)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000231
Guido van Rossum484772d1998-04-06 18:27:27 +0000232
Tim Peters2344fae2001-01-15 00:50:52 +0000233 def dele(self, which):
234 """Delete message number 'which'.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000235
Tim Peters2344fae2001-01-15 00:50:52 +0000236 Result is 'response'.
237 """
238 return self._shortcmd('DELE %s' % which)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000239
Guido van Rossum484772d1998-04-06 18:27:27 +0000240
Tim Peters2344fae2001-01-15 00:50:52 +0000241 def noop(self):
242 """Does nothing.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000243
Tim Peters2344fae2001-01-15 00:50:52 +0000244 One supposes the response indicates the server is alive.
245 """
246 return self._shortcmd('NOOP')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000247
Guido van Rossum484772d1998-04-06 18:27:27 +0000248
Tim Peters2344fae2001-01-15 00:50:52 +0000249 def rset(self):
250 """Not sure what this does."""
251 return self._shortcmd('RSET')
Guido van Rossum484772d1998-04-06 18:27:27 +0000252
Guido van Rossum03774bb1998-04-09 13:50:55 +0000253
Tim Peters2344fae2001-01-15 00:50:52 +0000254 def quit(self):
255 """Signoff: commit changes on server, unlock mailbox, close connection."""
256 try:
257 resp = self._shortcmd('QUIT')
258 except error_proto, val:
259 resp = val
260 self.file.close()
261 self.sock.close()
262 del self.file, self.sock
263 return resp
Guido van Rossum484772d1998-04-06 18:27:27 +0000264
Tim Peters2344fae2001-01-15 00:50:52 +0000265 #__del__ = quit
Guido van Rossum484772d1998-04-06 18:27:27 +0000266
Guido van Rossum484772d1998-04-06 18:27:27 +0000267
Tim Peters2344fae2001-01-15 00:50:52 +0000268 # optional commands:
Guido van Rossum03774bb1998-04-09 13:50:55 +0000269
Tim Peters2344fae2001-01-15 00:50:52 +0000270 def rpop(self, user):
271 """Not sure what this does."""
272 return self._shortcmd('RPOP %s' % user)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000273
Guido van Rossum03774bb1998-04-09 13:50:55 +0000274
Moshe Zadkaccc2e3d2001-01-19 19:56:27 +0000275 timestamp = re.compile(r'\+OK.*(<[^>]+>)')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000276
Tim Peters2344fae2001-01-15 00:50:52 +0000277 def apop(self, user, secret):
278 """Authorisation
Guido van Rossum03774bb1998-04-09 13:50:55 +0000279
Tim Peters2344fae2001-01-15 00:50:52 +0000280 - only possible if server has supplied a timestamp in initial greeting.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000281
Tim Peters2344fae2001-01-15 00:50:52 +0000282 Args:
283 user - mailbox user;
284 secret - secret shared between client and server.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000285
Tim Peters2344fae2001-01-15 00:50:52 +0000286 NB: mailbox is locked by server from here to 'quit()'
287 """
Moshe Zadkaccc2e3d2001-01-19 19:56:27 +0000288 m = self.timestamp.match(self.welcome)
289 if not m:
Tim Peters2344fae2001-01-15 00:50:52 +0000290 raise error_proto('-ERR APOP not supported by server')
291 import md5
Moshe Zadkaccc2e3d2001-01-19 19:56:27 +0000292 digest = md5.new(m.group(1)+secret).digest()
Eric S. Raymond341f9292001-02-09 06:56:56 +0000293 digest = ''.join(map(lambda x:'%02x'%ord(x), digest))
Tim Peters2344fae2001-01-15 00:50:52 +0000294 return self._shortcmd('APOP %s %s' % (user, digest))
Guido van Rossum03774bb1998-04-09 13:50:55 +0000295
Guido van Rossum03774bb1998-04-09 13:50:55 +0000296
Tim Peters2344fae2001-01-15 00:50:52 +0000297 def top(self, which, howmuch):
298 """Retrieve message header of message number 'which'
299 and first 'howmuch' lines of message body.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000300
Tim Peters2344fae2001-01-15 00:50:52 +0000301 Result is in form ['response', ['line', ...], octets].
302 """
303 return self._longcmd('TOP %s %s' % (which, howmuch))
Guido van Rossum03774bb1998-04-09 13:50:55 +0000304
Guido van Rossum03774bb1998-04-09 13:50:55 +0000305
Tim Peters2344fae2001-01-15 00:50:52 +0000306 def uidl(self, which=None):
307 """Return message digest (unique id) list.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000308
Tim Peters2344fae2001-01-15 00:50:52 +0000309 If 'which', result contains unique id for that message
310 in the form 'response mesgnum uid', otherwise result is
311 the list ['response', ['mesgnum uid', ...], octets]
312 """
313 if which:
314 return self._shortcmd('UIDL %s' % which)
315 return self._longcmd('UIDL')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000316
Guido van Rossum03774bb1998-04-09 13:50:55 +0000317
Guido van Rossum484772d1998-04-06 18:27:27 +0000318if __name__ == "__main__":
Eric S. Raymond341f9292001-02-09 06:56:56 +0000319 import sys
320 a = POP3(sys.argv[1])
Tim Peters2344fae2001-01-15 00:50:52 +0000321 print a.getwelcome()
Eric S. Raymond341f9292001-02-09 06:56:56 +0000322 a.user(sys.argv[2])
323 a.pass_(sys.argv[3])
Tim Peters2344fae2001-01-15 00:50:52 +0000324 a.list()
325 (numMsgs, totalSize) = a.stat()
326 for i in range(1, numMsgs + 1):
327 (header, msg, octets) = a.retr(i)
328 print "Message ", `i`, ':'
329 for line in msg:
330 print ' ' + line
331 print '-----------------------'
332 a.quit()