Results of a rewrite pass
[python/dscho.git] / Lib / poplib.py
blob0b22b2e4efe3ad52c224b99767a532e8a37ca957
1 """A POP3 client class.
3 Based on the J. Myers POP3 draft, Jan. 96
4 """
6 # 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 # String method conversion and test jig improvements by ESR, February 2001.
11 # Example (see the test function at the end of this file)
13 # Imports
15 import re, socket
17 __all__ = ["POP3","error_proto"]
19 # Exception raised when an error or invalid response is received:
21 class error_proto(Exception): pass
23 # Standard Port
24 POP3_PORT = 110
26 # Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
27 CR = '\r'
28 LF = '\n'
29 CRLF = CR+LF
32 class POP3:
34 """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.
38 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()
49 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)
55 Raises one exception: 'error_proto'.
57 Instantiate with:
58 POP3(hostname, port=110)
60 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.
65 POP is a line-based protocol, which means large mail
66 messages consume lots of python cycles reading them
67 line-by-line.
69 If it's available on your mail server, use IMAP4
70 instead, it doesn't suffer from the two problems
71 above.
72 """
75 def __init__(self, host, port = POP3_PORT):
76 self.host = host
77 self.port = port
78 msg = "getaddrinfo returns an empty list"
79 self.sock = None
80 for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
81 af, socktype, proto, canonname, sa = res
82 try:
83 self.sock = socket.socket(af, socktype, proto)
84 self.sock.connect(sa)
85 except socket.error, msg:
86 if self.sock:
87 self.sock.close()
88 self.sock = None
89 continue
90 break
91 if not self.sock:
92 raise socket.error, msg
93 self.file = self.sock.makefile('rb')
94 self._debugging = 0
95 self.welcome = self._getresp()
98 def _putline(self, line):
99 if self._debugging > 1: print '*put*', `line`
100 self.sock.sendall('%s%s' % (line, CRLF))
103 # Internal: send one command to the server (through _putline())
105 def _putcmd(self, line):
106 if self._debugging: print '*cmd*', `line`
107 self._putline(line)
110 # Internal: return one line from the server, stripping CRLF.
111 # This is where all the CPU time of this module is consumed.
112 # Raise error_proto('-ERR EOF') if the connection is closed.
114 def _getline(self):
115 line = self.file.readline()
116 if self._debugging > 1: print '*get*', `line`
117 if not line: raise error_proto('-ERR EOF')
118 octets = len(line)
119 # server can send any combination of CR & LF
120 # however, 'readline()' returns lines ending in LF
121 # so only possibilities are ...LF, ...CRLF, CR...LF
122 if line[-2:] == CRLF:
123 return line[:-2], octets
124 if line[0] == CR:
125 return line[1:-1], octets
126 return line[:-1], octets
129 # Internal: get a response from the server.
130 # Raise 'error_proto' if the response doesn't start with '+'.
132 def _getresp(self):
133 resp, o = self._getline()
134 if self._debugging > 1: print '*resp*', `resp`
135 c = resp[:1]
136 if c != '+':
137 raise error_proto(resp)
138 return resp
141 # Internal: get a response plus following text from the server.
143 def _getlongresp(self):
144 resp = self._getresp()
145 list = []; octets = 0
146 line, o = self._getline()
147 while line != '.':
148 if line[:2] == '..':
149 o = o-1
150 line = line[1:]
151 octets = octets + o
152 list.append(line)
153 line, o = self._getline()
154 return resp, list, octets
157 # Internal: send a command and get the response
159 def _shortcmd(self, line):
160 self._putcmd(line)
161 return self._getresp()
164 # Internal: send a command and get the response plus following text
166 def _longcmd(self, line):
167 self._putcmd(line)
168 return self._getlongresp()
171 # These can be useful:
173 def getwelcome(self):
174 return self.welcome
177 def set_debuglevel(self, level):
178 self._debugging = level
181 # Here are all the POP commands:
183 def user(self, user):
184 """Send user name, return response
186 (should indicate password required).
188 return self._shortcmd('USER %s' % user)
191 def pass_(self, pswd):
192 """Send password, return response
194 (response includes message count, mailbox size).
196 NB: mailbox is locked by server from here to 'quit()'
198 return self._shortcmd('PASS %s' % pswd)
201 def stat(self):
202 """Get mailbox status.
204 Result is tuple of 2 ints (message count, mailbox size)
206 retval = self._shortcmd('STAT')
207 rets = retval.split()
208 if self._debugging: print '*stat*', `rets`
209 numMessages = int(rets[1])
210 sizeMessages = int(rets[2])
211 return (numMessages, sizeMessages)
214 def list(self, which=None):
215 """Request listing, return result.
217 Result without a message number argument is in form
218 ['response', ['mesg_num octets', ...]].
220 Result when a message number argument is given is a
221 single response: the "scan listing" for that message.
223 if which is not None:
224 return self._shortcmd('LIST %s' % which)
225 return self._longcmd('LIST')
228 def retr(self, which):
229 """Retrieve whole message number 'which'.
231 Result is in form ['response', ['line', ...], octets].
233 return self._longcmd('RETR %s' % which)
236 def dele(self, which):
237 """Delete message number 'which'.
239 Result is 'response'.
241 return self._shortcmd('DELE %s' % which)
244 def noop(self):
245 """Does nothing.
247 One supposes the response indicates the server is alive.
249 return self._shortcmd('NOOP')
252 def rset(self):
253 """Not sure what this does."""
254 return self._shortcmd('RSET')
257 def quit(self):
258 """Signoff: commit changes on server, unlock mailbox, close connection."""
259 try:
260 resp = self._shortcmd('QUIT')
261 except error_proto, val:
262 resp = val
263 self.file.close()
264 self.sock.close()
265 del self.file, self.sock
266 return resp
268 #__del__ = quit
271 # optional commands:
273 def rpop(self, user):
274 """Not sure what this does."""
275 return self._shortcmd('RPOP %s' % user)
278 timestamp = re.compile(r'\+OK.*(<[^>]+>)')
280 def apop(self, user, secret):
281 """Authorisation
283 - only possible if server has supplied a timestamp in initial greeting.
285 Args:
286 user - mailbox user;
287 secret - secret shared between client and server.
289 NB: mailbox is locked by server from here to 'quit()'
291 m = self.timestamp.match(self.welcome)
292 if not m:
293 raise error_proto('-ERR APOP not supported by server')
294 import md5
295 digest = md5.new(m.group(1)+secret).digest()
296 digest = ''.join(map(lambda x:'%02x'%ord(x), digest))
297 return self._shortcmd('APOP %s %s' % (user, digest))
300 def top(self, which, howmuch):
301 """Retrieve message header of message number 'which'
302 and first 'howmuch' lines of message body.
304 Result is in form ['response', ['line', ...], octets].
306 return self._longcmd('TOP %s %s' % (which, howmuch))
309 def uidl(self, which=None):
310 """Return message digest (unique id) list.
312 If 'which', result contains unique id for that message
313 in the form 'response mesgnum uid', otherwise result is
314 the list ['response', ['mesgnum uid', ...], octets]
316 if which is not None:
317 return self._shortcmd('UIDL %s' % which)
318 return self._longcmd('UIDL')
321 if __name__ == "__main__":
322 import sys
323 a = POP3(sys.argv[1])
324 print a.getwelcome()
325 a.user(sys.argv[2])
326 a.pass_(sys.argv[3])
327 a.list()
328 (numMsgs, totalSize) = a.stat()
329 for i in range(1, numMsgs + 1):
330 (header, msg, octets) = a.retr(i)
331 print "Message ", `i`, ':'
332 for line in msg:
333 print ' ' + line
334 print '-----------------------'
335 a.quit()