Clarify portability and main program.
[python/dscho.git] / Lib / poplib.py
blob7b13b465718f39886e5c9ca4e2033774832d3a5b
1 """A POP3 client class.
3 Based on the J. Myers POP3 draft, Jan. 96
5 Author: David Ascher <david_ascher@brown.edu>
6 [heavily stealing from nntplib.py]
7 Updated: Piers Lauder <piers@cs.su.oz.au> [Jul '97]
8 """
10 # Example (see the test function at the end of this file)
12 TESTSERVER = "localhost"
13 TESTACCOUNT = "test"
14 TESTPASSWORD = "_passwd_"
16 # Imports
18 import regex, socket, string
20 # Exception raised when an error or invalid response is received:
22 class error_proto(Exception): pass
24 # Standard Port
25 POP3_PORT = 110
27 # Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
28 CR = '\r'
29 LF = '\n'
30 CRLF = CR+LF
33 class POP3:
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.
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()
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)
56 Raises one exception: 'error_proto'.
58 Instantiate with:
59 POP3(hostname, port=110)
61 NB: the POP protocol locks the mailbox from user
62 authorisation until QUIT, so be sure to get in, suck
63 the messages, and quit, each time you access the
64 mailbox.
66 POP is a line-based protocol, which means large mail
67 messages consume lots of python cycles reading them
68 line-by-line.
70 If it's available on your mail server, use IMAP4
71 instead, it doesn't suffer from the two problems
72 above.
73 """
76 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()
86 def _putline(self, line):
87 #if self._debugging > 1: print '*put*', `line`
88 self.sock.send('%s%s' % (line, CRLF))
91 # Internal: send one command to the server (through _putline())
93 def _putcmd(self, line):
94 #if self._debugging: print '*cmd*', `line`
95 self._putline(line)
98 # 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.
102 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
117 # Internal: get a response from the server.
118 # Raise 'error_proto' if the response doesn't start with '+'.
120 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
129 # Internal: get a response plus following text from the server.
131 def _getlongresp(self):
132 resp = self._getresp()
133 list = []; octets = 0
134 line, o = self._getline()
135 while line != '.':
136 octets = octets + o
137 list.append(line)
138 line, o = self._getline()
139 return resp, list, octets
142 # Internal: send a command and get the response
144 def _shortcmd(self, line):
145 self._putcmd(line)
146 return self._getresp()
149 # Internal: send a command and get the response plus following text
151 def _longcmd(self, line):
152 self._putcmd(line)
153 return self._getlongresp()
156 # These can be useful:
158 def getwelcome(self):
159 return self.welcome
162 def set_debuglevel(self, level):
163 self._debugging = level
166 # Here are all the POP commands:
168 def user(self, user):
169 """Send user name, return response
171 (should indicate password required).
173 return self._shortcmd('USER %s' % user)
176 def pass_(self, pswd):
177 """Send password, return response
179 (response includes message count, mailbox size).
181 NB: mailbox is locked by server from here to 'quit()'
183 return self._shortcmd('PASS %s' % pswd)
186 def stat(self):
187 """Get mailbox status.
189 Result is tuple of 2 ints (message count, mailbox size)
191 retval = self._shortcmd('STAT')
192 rets = string.split(retval)
193 #if self._debugging: print '*stat*', `rets`
194 numMessages = string.atoi(rets[1])
195 sizeMessages = string.atoi(rets[2])
196 return (numMessages, sizeMessages)
199 def list(self, which=None):
200 """Request listing, return result.
202 Result without a message number argument is in form
203 ['response', ['mesg_num octets', ...]].
205 Result when a message number argument is given is a
206 single response: the "scan listing" for that message.
208 if which:
209 return self._shortcmd('LIST %s' % which)
210 return self._longcmd('LIST')
213 def retr(self, which):
214 """Retrieve whole message number 'which'.
216 Result is in form ['response', ['line', ...], octets].
218 return self._longcmd('RETR %s' % which)
221 def dele(self, which):
222 """Delete message number 'which'.
224 Result is 'response'.
226 return self._shortcmd('DELE %s' % which)
229 def noop(self):
230 """Does nothing.
232 One supposes the response indicates the server is alive.
234 return self._shortcmd('NOOP')
237 def rset(self):
238 """Not sure what this does."""
239 return self._shortcmd('RSET')
242 def quit(self):
243 """Signoff: commit changes on server, unlock mailbox, close connection."""
244 try:
245 resp = self._shortcmd('QUIT')
246 except error_proto, val:
247 resp = val
248 self.file.close()
249 self.sock.close()
250 del self.file, self.sock
251 return resp
253 #__del__ = quit
256 # optional commands:
258 def rpop(self, user):
259 """Not sure what this does."""
260 return self._shortcmd('RPOP %s' % user)
263 timestamp = regex.compile('\+OK.*\(<[^>]+>\)')
265 def apop(self, user, secret):
266 """Authorisation
268 - only possible if server has supplied a timestamp in initial greeting.
270 Args:
271 user - mailbox user;
272 secret - secret shared between client and server.
274 NB: mailbox is locked by server from here to 'quit()'
276 if self.timestamp.match(self.welcome) <= 0:
277 raise error_proto('-ERR APOP not supported by server')
278 import md5
279 digest = md5.new(self.timestamp.group(1)+secret).digest()
280 digest = string.join(map(lambda x:'%02x'%ord(x), digest), '')
281 return self._shortcmd('APOP %s %s' % (user, digest))
284 def top(self, which, howmuch):
285 """Retrieve message header of message number 'which'
286 and first 'howmuch' lines of message body.
288 Result is in form ['response', ['line', ...], octets].
290 return self._longcmd('TOP %s %s' % (which, howmuch))
293 def uidl(self, which=None):
294 """Return message digest (unique id) list.
296 If 'which', result contains unique id for that message,
297 otherwise result is list ['response', ['mesgnum uid', ...], octets]
299 if which:
300 return self._shortcmd('UIDL %s' % which)
301 return self._longcmd('UIDL')
304 if __name__ == "__main__":
305 a = POP3(TESTSERVER)
306 print a.getwelcome()
307 a.user(TESTACCOUNT)
308 a.pass_(TESTPASSWORD)
309 a.list()
310 (numMsgs, totalSize) = a.stat()
311 for i in range(1, numMsgs + 1):
312 (header, msg, octets) = a.retr(i)
313 print "Message ", `i`, ':'
314 for line in msg:
315 print ' ' + line
316 print '-----------------------'
317 a.quit()