1 """A POP3 client class.
3 Based on the J. Myers POP3 draft, Jan. 96
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.
10 # Added the POP3_SSL class. Methods loosely based on IMAP_SSL. Hector Urtubia <urtubia@mrbook.org> Aug 2003
12 # Example (see the test function at the end of this file)
18 __all__
= ["POP3","error_proto","POP3_SSL"]
20 # Exception raised when an error or invalid response is received:
22 class error_proto(Exception): pass
30 # Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF)
38 """This class supports both the minimal and optional command sets.
39 Arguments can be strings or integers (where appropriate)
40 (e.g.: retr(1) and retr('1') both work equally well.
44 PASS string pass_(string)
46 LIST [msg] list(msg = None)
53 Optional Commands (some servers support these):
55 APOP name digest apop(name, digest)
57 UIDL [msg] uidl(msg = None)
59 Raises one exception: 'error_proto'.
62 POP3(hostname, port=110)
64 NB: the POP protocol locks the mailbox from user
65 authorization until QUIT, so be sure to get in, suck
66 the messages, and quit, each time you access the
69 POP is a line-based protocol, which means large mail
70 messages consume lots of python cycles reading them
73 If it's available on your mail server, use IMAP4
74 instead, it doesn't suffer from the two problems
79 def __init__(self
, host
, port
= POP3_PORT
):
82 msg
= "getaddrinfo returns an empty list"
84 for res
in socket
.getaddrinfo(self
.host
, self
.port
, 0, socket
.SOCK_STREAM
):
85 af
, socktype
, proto
, canonname
, sa
= res
87 self
.sock
= socket
.socket(af
, socktype
, proto
)
89 except socket
.error
, msg
:
96 raise socket
.error
, msg
97 self
.file = self
.sock
.makefile('rb')
99 self
.welcome
= self
._getresp
()
102 def _putline(self
, line
):
103 if self
._debugging
> 1: print '*put*', repr(line
)
104 self
.sock
.sendall('%s%s' % (line
, CRLF
))
107 # Internal: send one command to the server (through _putline())
109 def _putcmd(self
, line
):
110 if self
._debugging
: print '*cmd*', repr(line
)
114 # Internal: return one line from the server, stripping CRLF.
115 # This is where all the CPU time of this module is consumed.
116 # Raise error_proto('-ERR EOF') if the connection is closed.
119 line
= self
.file.readline()
120 if self
._debugging
> 1: print '*get*', repr(line
)
121 if not line
: raise error_proto('-ERR EOF')
123 # server can send any combination of CR & LF
124 # however, 'readline()' returns lines ending in LF
125 # so only possibilities are ...LF, ...CRLF, CR...LF
126 if line
[-2:] == CRLF
:
127 return line
[:-2], octets
129 return line
[1:-1], octets
130 return line
[:-1], octets
133 # Internal: get a response from the server.
134 # Raise 'error_proto' if the response doesn't start with '+'.
137 resp
, o
= self
._getline
()
138 if self
._debugging
> 1: print '*resp*', repr(resp
)
141 raise error_proto(resp
)
145 # Internal: get a response plus following text from the server.
147 def _getlongresp(self
):
148 resp
= self
._getresp
()
149 list = []; octets
= 0
150 line
, o
= self
._getline
()
157 line
, o
= self
._getline
()
158 return resp
, list, octets
161 # Internal: send a command and get the response
163 def _shortcmd(self
, line
):
165 return self
._getresp
()
168 # Internal: send a command and get the response plus following text
170 def _longcmd(self
, line
):
172 return self
._getlongresp
()
175 # These can be useful:
177 def getwelcome(self
):
181 def set_debuglevel(self
, level
):
182 self
._debugging
= level
185 # Here are all the POP commands:
187 def user(self
, user
):
188 """Send user name, return response
190 (should indicate password required).
192 return self
._shortcmd
('USER %s' % user
)
195 def pass_(self
, pswd
):
196 """Send password, return response
198 (response includes message count, mailbox size).
200 NB: mailbox is locked by server from here to 'quit()'
202 return self
._shortcmd
('PASS %s' % pswd
)
206 """Get mailbox status.
208 Result is tuple of 2 ints (message count, mailbox size)
210 retval
= self
._shortcmd
('STAT')
211 rets
= retval
.split()
212 if self
._debugging
: print '*stat*', repr(rets
)
213 numMessages
= int(rets
[1])
214 sizeMessages
= int(rets
[2])
215 return (numMessages
, sizeMessages
)
218 def list(self
, which
=None):
219 """Request listing, return result.
221 Result without a message number argument is in form
222 ['response', ['mesg_num octets', ...], octets].
224 Result when a message number argument is given is a
225 single response: the "scan listing" for that message.
227 if which
is not None:
228 return self
._shortcmd
('LIST %s' % which
)
229 return self
._longcmd
('LIST')
232 def retr(self
, which
):
233 """Retrieve whole message number 'which'.
235 Result is in form ['response', ['line', ...], octets].
237 return self
._longcmd
('RETR %s' % which
)
240 def dele(self
, which
):
241 """Delete message number 'which'.
243 Result is 'response'.
245 return self
._shortcmd
('DELE %s' % which
)
251 One supposes the response indicates the server is alive.
253 return self
._shortcmd
('NOOP')
257 """Not sure what this does."""
258 return self
._shortcmd
('RSET')
262 """Signoff: commit changes on server, unlock mailbox, close connection."""
264 resp
= self
._shortcmd
('QUIT')
265 except error_proto
, val
:
269 del self
.file, self
.sock
277 def rpop(self
, user
):
278 """Not sure what this does."""
279 return self
._shortcmd
('RPOP %s' % user
)
282 timestamp
= re
.compile(r
'\+OK.*(<[^>]+>)')
284 def apop(self
, user
, secret
):
287 - only possible if server has supplied a timestamp in initial greeting.
291 secret - secret shared between client and server.
293 NB: mailbox is locked by server from here to 'quit()'
295 m
= self
.timestamp
.match(self
.welcome
)
297 raise error_proto('-ERR APOP not supported by server')
299 digest
= hashlib
.md5(m
.group(1)+secret
).digest()
300 digest
= ''.join(map(lambda x
:'%02x'%ord(x
), digest
))
301 return self
._shortcmd
('APOP %s %s' % (user
, digest
))
304 def top(self
, which
, howmuch
):
305 """Retrieve message header of message number 'which'
306 and first 'howmuch' lines of message body.
308 Result is in form ['response', ['line', ...], octets].
310 return self
._longcmd
('TOP %s %s' % (which
, howmuch
))
313 def uidl(self
, which
=None):
314 """Return message digest (unique id) list.
316 If 'which', result contains unique id for that message
317 in the form 'response mesgnum uid', otherwise result is
318 the list ['response', ['mesgnum uid', ...], octets]
320 if which
is not None:
321 return self
._shortcmd
('UIDL %s' % which
)
322 return self
._longcmd
('UIDL')
324 class POP3_SSL(POP3
):
325 """POP3 client class over SSL connection
327 Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None)
329 hostname - the hostname of the pop3 over ssl server
331 keyfile - PEM formatted file that countains your private key
332 certfile - PEM formatted certificate chain file
334 See the methods of the parent class POP3 for more documentation.
337 def __init__(self
, host
, port
= POP3_SSL_PORT
, keyfile
= None, certfile
= None):
340 self
.keyfile
= keyfile
341 self
.certfile
= certfile
343 msg
= "getaddrinfo returns an empty list"
345 for res
in socket
.getaddrinfo(self
.host
, self
.port
, 0, socket
.SOCK_STREAM
):
346 af
, socktype
, proto
, canonname
, sa
= res
348 self
.sock
= socket
.socket(af
, socktype
, proto
)
349 self
.sock
.connect(sa
)
350 except socket
.error
, msg
:
357 raise socket
.error
, msg
358 self
.file = self
.sock
.makefile('rb')
359 self
.sslobj
= socket
.ssl(self
.sock
, self
.keyfile
, self
.certfile
)
361 self
.welcome
= self
._getresp
()
363 def _fillBuffer(self
):
364 localbuf
= self
.sslobj
.read()
365 if len(localbuf
) == 0:
366 raise error_proto('-ERR EOF')
367 self
.buffer += localbuf
371 renewline
= re
.compile(r
'.*?\n')
372 match
= renewline
.match(self
.buffer)
375 match
= renewline
.match(self
.buffer)
376 line
= match
.group(0)
377 self
.buffer = renewline
.sub('' ,self
.buffer, 1)
378 if self
._debugging
> 1: print '*get*', repr(line
)
381 if line
[-2:] == CRLF
:
382 return line
[:-2], octets
384 return line
[1:-1], octets
385 return line
[:-1], octets
387 def _putline(self
, line
):
388 if self
._debugging
> 1: print '*put*', repr(line
)
392 sent
= self
.sslobj
.write(line
)
399 """Signoff: commit changes on server, unlock mailbox, close connection."""
401 resp
= self
._shortcmd
('QUIT')
402 except error_proto
, val
:
405 del self
.sslobj
, self
.sock
409 if __name__
== "__main__":
411 a
= POP3(sys
.argv
[1])
416 (numMsgs
, totalSize
) = a
.stat()
417 for i
in range(1, numMsgs
+ 1):
418 (header
, msg
, octets
) = a
.retr(i
)
419 print "Message %d:" % i
422 print '-----------------------'