blob: fb24a0f9c5b83c655d305ea31178426a87ede990 [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):
76 self.host = host
77 self.port = port
78 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
79 self.sock.connect((self.host, self.port))
80 self.file = self.sock.makefile('rb')
81 self._debugging = 0
82 self.welcome = self._getresp()
Guido van Rossum484772d1998-04-06 18:27:27 +000083
Guido van Rossum03774bb1998-04-09 13:50:55 +000084
Tim Peters2344fae2001-01-15 00:50:52 +000085 def _putline(self, line):
86 #if self._debugging > 1: print '*put*', `line`
87 self.sock.send('%s%s' % (line, CRLF))
Guido van Rossum03774bb1998-04-09 13:50:55 +000088
Guido van Rossum484772d1998-04-06 18:27:27 +000089
Tim Peters2344fae2001-01-15 00:50:52 +000090 # Internal: send one command to the server (through _putline())
Guido van Rossum03774bb1998-04-09 13:50:55 +000091
Tim Peters2344fae2001-01-15 00:50:52 +000092 def _putcmd(self, line):
93 #if self._debugging: print '*cmd*', `line`
94 self._putline(line)
Guido van Rossum484772d1998-04-06 18:27:27 +000095
Guido van Rossum03774bb1998-04-09 13:50:55 +000096
Tim Peters2344fae2001-01-15 00:50:52 +000097 # Internal: return one line from the server, stripping CRLF.
98 # This is where all the CPU time of this module is consumed.
99 # Raise error_proto('-ERR EOF') if the connection is closed.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000100
Tim Peters2344fae2001-01-15 00:50:52 +0000101 def _getline(self):
102 line = self.file.readline()
103 #if self._debugging > 1: print '*get*', `line`
104 if not line: raise error_proto('-ERR EOF')
105 octets = len(line)
106 # server can send any combination of CR & LF
107 # however, 'readline()' returns lines ending in LF
108 # so only possibilities are ...LF, ...CRLF, CR...LF
109 if line[-2:] == CRLF:
110 return line[:-2], octets
111 if line[0] == CR:
112 return line[1:-1], octets
113 return line[:-1], octets
Guido van Rossum03774bb1998-04-09 13:50:55 +0000114
Guido van Rossum484772d1998-04-06 18:27:27 +0000115
Tim Peters2344fae2001-01-15 00:50:52 +0000116 # Internal: get a response from the server.
117 # Raise 'error_proto' if the response doesn't start with '+'.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000118
Tim Peters2344fae2001-01-15 00:50:52 +0000119 def _getresp(self):
120 resp, o = self._getline()
121 #if self._debugging > 1: print '*resp*', `resp`
122 c = resp[:1]
123 if c != '+':
124 raise error_proto(resp)
125 return resp
Guido van Rossum484772d1998-04-06 18:27:27 +0000126
Guido van Rossum03774bb1998-04-09 13:50:55 +0000127
Tim Peters2344fae2001-01-15 00:50:52 +0000128 # Internal: get a response plus following text from the server.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000129
Tim Peters2344fae2001-01-15 00:50:52 +0000130 def _getlongresp(self):
131 resp = self._getresp()
132 list = []; octets = 0
133 line, o = self._getline()
134 while line != '.':
135 if line[:2] == '..':
136 o = o-1
137 line = line[1:]
138 octets = octets + o
139 list.append(line)
140 line, o = self._getline()
141 return resp, list, octets
Guido van Rossum03774bb1998-04-09 13:50:55 +0000142
Guido van Rossum484772d1998-04-06 18:27:27 +0000143
Tim Peters2344fae2001-01-15 00:50:52 +0000144 # Internal: send a command and get the response
Guido van Rossum03774bb1998-04-09 13:50:55 +0000145
Tim Peters2344fae2001-01-15 00:50:52 +0000146 def _shortcmd(self, line):
147 self._putcmd(line)
148 return self._getresp()
Guido van Rossum484772d1998-04-06 18:27:27 +0000149
Guido van Rossum03774bb1998-04-09 13:50:55 +0000150
Tim Peters2344fae2001-01-15 00:50:52 +0000151 # Internal: send a command and get the response plus following text
Guido van Rossum03774bb1998-04-09 13:50:55 +0000152
Tim Peters2344fae2001-01-15 00:50:52 +0000153 def _longcmd(self, line):
154 self._putcmd(line)
155 return self._getlongresp()
Guido van Rossum484772d1998-04-06 18:27:27 +0000156
Guido van Rossum03774bb1998-04-09 13:50:55 +0000157
Tim Peters2344fae2001-01-15 00:50:52 +0000158 # These can be useful:
159
160 def getwelcome(self):
161 return self.welcome
162
163
164 def set_debuglevel(self, level):
165 self._debugging = level
166
167
168 # Here are all the POP commands:
169
170 def user(self, user):
171 """Send user name, return response
Guido van Rossum484772d1998-04-06 18:27:27 +0000172
Tim Peters2344fae2001-01-15 00:50:52 +0000173 (should indicate password required).
174 """
175 return self._shortcmd('USER %s' % user)
Guido van Rossum484772d1998-04-06 18:27:27 +0000176
Guido van Rossum03774bb1998-04-09 13:50:55 +0000177
Tim Peters2344fae2001-01-15 00:50:52 +0000178 def pass_(self, pswd):
179 """Send password, return response
Guido van Rossum484772d1998-04-06 18:27:27 +0000180
Tim Peters2344fae2001-01-15 00:50:52 +0000181 (response includes message count, mailbox size).
Guido van Rossum03774bb1998-04-09 13:50:55 +0000182
Tim Peters2344fae2001-01-15 00:50:52 +0000183 NB: mailbox is locked by server from here to 'quit()'
184 """
185 return self._shortcmd('PASS %s' % pswd)
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 stat(self):
189 """Get mailbox status.
Guido van Rossum484772d1998-04-06 18:27:27 +0000190
Tim Peters2344fae2001-01-15 00:50:52 +0000191 Result is tuple of 2 ints (message count, mailbox size)
192 """
193 retval = self._shortcmd('STAT')
Eric S. Raymond341f9292001-02-09 06:56:56 +0000194 rets = retval.split()
Tim Peters2344fae2001-01-15 00:50:52 +0000195 #if self._debugging: print '*stat*', `rets`
Eric S. Raymond341f9292001-02-09 06:56:56 +0000196 numMessages = int(rets[1])
197 sizeMessages = int(rets[2])
Tim Peters2344fae2001-01-15 00:50:52 +0000198 return (numMessages, sizeMessages)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000199
Guido van Rossum03774bb1998-04-09 13:50:55 +0000200
Tim Peters2344fae2001-01-15 00:50:52 +0000201 def list(self, which=None):
202 """Request listing, return result.
Guido van Rossum484772d1998-04-06 18:27:27 +0000203
Tim Peters2344fae2001-01-15 00:50:52 +0000204 Result without a message number argument is in form
205 ['response', ['mesg_num octets', ...]].
Guido van Rossum484772d1998-04-06 18:27:27 +0000206
Tim Peters2344fae2001-01-15 00:50:52 +0000207 Result when a message number argument is given is a
208 single response: the "scan listing" for that message.
209 """
210 if which:
211 return self._shortcmd('LIST %s' % which)
212 return self._longcmd('LIST')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000213
Guido van Rossum03774bb1998-04-09 13:50:55 +0000214
Tim Peters2344fae2001-01-15 00:50:52 +0000215 def retr(self, which):
216 """Retrieve whole message number 'which'.
Guido van Rossumf6ae7431998-09-02 14:42:02 +0000217
Tim Peters2344fae2001-01-15 00:50:52 +0000218 Result is in form ['response', ['line', ...], octets].
219 """
220 return self._longcmd('RETR %s' % which)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000221
Guido van Rossum484772d1998-04-06 18:27:27 +0000222
Tim Peters2344fae2001-01-15 00:50:52 +0000223 def dele(self, which):
224 """Delete message number 'which'.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000225
Tim Peters2344fae2001-01-15 00:50:52 +0000226 Result is 'response'.
227 """
228 return self._shortcmd('DELE %s' % which)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000229
Guido van Rossum484772d1998-04-06 18:27:27 +0000230
Tim Peters2344fae2001-01-15 00:50:52 +0000231 def noop(self):
232 """Does nothing.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000233
Tim Peters2344fae2001-01-15 00:50:52 +0000234 One supposes the response indicates the server is alive.
235 """
236 return self._shortcmd('NOOP')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000237
Guido van Rossum484772d1998-04-06 18:27:27 +0000238
Tim Peters2344fae2001-01-15 00:50:52 +0000239 def rset(self):
240 """Not sure what this does."""
241 return self._shortcmd('RSET')
Guido van Rossum484772d1998-04-06 18:27:27 +0000242
Guido van Rossum03774bb1998-04-09 13:50:55 +0000243
Tim Peters2344fae2001-01-15 00:50:52 +0000244 def quit(self):
245 """Signoff: commit changes on server, unlock mailbox, close connection."""
246 try:
247 resp = self._shortcmd('QUIT')
248 except error_proto, val:
249 resp = val
250 self.file.close()
251 self.sock.close()
252 del self.file, self.sock
253 return resp
Guido van Rossum484772d1998-04-06 18:27:27 +0000254
Tim Peters2344fae2001-01-15 00:50:52 +0000255 #__del__ = quit
Guido van Rossum484772d1998-04-06 18:27:27 +0000256
Guido van Rossum484772d1998-04-06 18:27:27 +0000257
Tim Peters2344fae2001-01-15 00:50:52 +0000258 # optional commands:
Guido van Rossum03774bb1998-04-09 13:50:55 +0000259
Tim Peters2344fae2001-01-15 00:50:52 +0000260 def rpop(self, user):
261 """Not sure what this does."""
262 return self._shortcmd('RPOP %s' % user)
Guido van Rossum03774bb1998-04-09 13:50:55 +0000263
Guido van Rossum03774bb1998-04-09 13:50:55 +0000264
Moshe Zadkaccc2e3d2001-01-19 19:56:27 +0000265 timestamp = re.compile(r'\+OK.*(<[^>]+>)')
Guido van Rossum03774bb1998-04-09 13:50:55 +0000266
Tim Peters2344fae2001-01-15 00:50:52 +0000267 def apop(self, user, secret):
268 """Authorisation
Guido van Rossum03774bb1998-04-09 13:50:55 +0000269
Tim Peters2344fae2001-01-15 00:50:52 +0000270 - only possible if server has supplied a timestamp in initial greeting.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000271
Tim Peters2344fae2001-01-15 00:50:52 +0000272 Args:
273 user - mailbox user;
274 secret - secret shared between client and server.
Guido van Rossum03774bb1998-04-09 13:50:55 +0000275
Tim Peters2344fae2001-01-15 00:50:52 +0000276 NB: mailbox is locked by server from here to 'quit()'
277 """
Moshe Zadkaccc2e3d2001-01-19 19:56:27 +0000278 m = self.timestamp.match(self.welcome)
279 if not m:
Tim Peters2344fae2001-01-15 00:50:52 +0000280 raise error_proto('-ERR APOP not supported by server')
281 import md5
Moshe Zadkaccc2e3d2001-01-19 19:56:27 +0000282 digest = md5.new(m.group(1)+secret).digest()
Eric S. Raymond341f9292001-02-09 06:56:56 +0000283 digest = ''.join(map(lambda x:'%02x'%ord(x), digest))
Tim Peters2344fae2001-01-15 00:50:52 +0000284 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__":
Eric S. Raymond341f9292001-02-09 06:56:56 +0000309 import sys
310 a = POP3(sys.argv[1])
Tim Peters2344fae2001-01-15 00:50:52 +0000311 print a.getwelcome()
Eric S. Raymond341f9292001-02-09 06:56:56 +0000312 a.user(sys.argv[2])
313 a.pass_(sys.argv[3])
Tim Peters2344fae2001-01-15 00:50:52 +0000314 a.list()
315 (numMsgs, totalSize) = a.stat()
316 for i in range(1, numMsgs + 1):
317 (header, msg, octets) = a.retr(i)
318 print "Message ", `i`, ':'
319 for line in msg:
320 print ' ' + line
321 print '-----------------------'
322 a.quit()