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
, timeout
=None):
82 self
.sock
= socket
.create_connection((host
, port
), timeout
)
83 self
.file = self
.sock
.makefile('rb')
85 self
.welcome
= self
._getresp
()
88 def _putline(self
, line
):
89 if self
._debugging
> 1: print '*put*', repr(line
)
90 self
.sock
.sendall('%s%s' % (line
, CRLF
))
93 # Internal: send one command to the server (through _putline())
95 def _putcmd(self
, line
):
96 if self
._debugging
: print '*cmd*', repr(line
)
100 # Internal: return one line from the server, stripping CRLF.
101 # This is where all the CPU time of this module is consumed.
102 # Raise error_proto('-ERR EOF') if the connection is closed.
105 line
= self
.file.readline()
106 if self
._debugging
> 1: print '*get*', repr(line
)
107 if not line
: raise error_proto('-ERR EOF')
109 # server can send any combination of CR & LF
110 # however, 'readline()' returns lines ending in LF
111 # so only possibilities are ...LF, ...CRLF, CR...LF
112 if line
[-2:] == CRLF
:
113 return line
[:-2], octets
115 return line
[1:-1], octets
116 return line
[:-1], octets
119 # Internal: get a response from the server.
120 # Raise 'error_proto' if the response doesn't start with '+'.
123 resp
, o
= self
._getline
()
124 if self
._debugging
> 1: print '*resp*', repr(resp
)
127 raise error_proto(resp
)
131 # Internal: get a response plus following text from the server.
133 def _getlongresp(self
):
134 resp
= self
._getresp
()
135 list = []; octets
= 0
136 line
, o
= self
._getline
()
143 line
, o
= self
._getline
()
144 return resp
, list, octets
147 # Internal: send a command and get the response
149 def _shortcmd(self
, line
):
151 return self
._getresp
()
154 # Internal: send a command and get the response plus following text
156 def _longcmd(self
, line
):
158 return self
._getlongresp
()
161 # These can be useful:
163 def getwelcome(self
):
167 def set_debuglevel(self
, level
):
168 self
._debugging
= level
171 # Here are all the POP commands:
173 def user(self
, user
):
174 """Send user name, return response
176 (should indicate password required).
178 return self
._shortcmd
('USER %s' % user
)
181 def pass_(self
, pswd
):
182 """Send password, return response
184 (response includes message count, mailbox size).
186 NB: mailbox is locked by server from here to 'quit()'
188 return self
._shortcmd
('PASS %s' % pswd
)
192 """Get mailbox status.
194 Result is tuple of 2 ints (message count, mailbox size)
196 retval
= self
._shortcmd
('STAT')
197 rets
= retval
.split()
198 if self
._debugging
: print '*stat*', repr(rets
)
199 numMessages
= int(rets
[1])
200 sizeMessages
= int(rets
[2])
201 return (numMessages
, sizeMessages
)
204 def list(self
, which
=None):
205 """Request listing, return result.
207 Result without a message number argument is in form
208 ['response', ['mesg_num octets', ...], octets].
210 Result when a message number argument is given is a
211 single response: the "scan listing" for that message.
213 if which
is not None:
214 return self
._shortcmd
('LIST %s' % which
)
215 return self
._longcmd
('LIST')
218 def retr(self
, which
):
219 """Retrieve whole message number 'which'.
221 Result is in form ['response', ['line', ...], octets].
223 return self
._longcmd
('RETR %s' % which
)
226 def dele(self
, which
):
227 """Delete message number 'which'.
229 Result is 'response'.
231 return self
._shortcmd
('DELE %s' % which
)
237 One supposes the response indicates the server is alive.
239 return self
._shortcmd
('NOOP')
243 """Not sure what this does."""
244 return self
._shortcmd
('RSET')
248 """Signoff: commit changes on server, unlock mailbox, close connection."""
250 resp
= self
._shortcmd
('QUIT')
251 except error_proto
, val
:
255 del self
.file, self
.sock
263 def rpop(self
, user
):
264 """Not sure what this does."""
265 return self
._shortcmd
('RPOP %s' % user
)
268 timestamp
= re
.compile(r
'\+OK.*(<[^>]+>)')
270 def apop(self
, user
, secret
):
273 - only possible if server has supplied a timestamp in initial greeting.
277 secret - secret shared between client and server.
279 NB: mailbox is locked by server from here to 'quit()'
281 m
= self
.timestamp
.match(self
.welcome
)
283 raise error_proto('-ERR APOP not supported by server')
285 digest
= hashlib
.md5(m
.group(1)+secret
).digest()
286 digest
= ''.join(map(lambda x
:'%02x'%ord(x
), digest
))
287 return self
._shortcmd
('APOP %s %s' % (user
, digest
))
290 def top(self
, which
, howmuch
):
291 """Retrieve message header of message number 'which'
292 and first 'howmuch' lines of message body.
294 Result is in form ['response', ['line', ...], octets].
296 return self
._longcmd
('TOP %s %s' % (which
, howmuch
))
299 def uidl(self
, which
=None):
300 """Return message digest (unique id) list.
302 If 'which', result contains unique id for that message
303 in the form 'response mesgnum uid', otherwise result is
304 the list ['response', ['mesgnum uid', ...], octets]
306 if which
is not None:
307 return self
._shortcmd
('UIDL %s' % which
)
308 return self
._longcmd
('UIDL')
310 class POP3_SSL(POP3
):
311 """POP3 client class over SSL connection
313 Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None)
315 hostname - the hostname of the pop3 over ssl server
317 keyfile - PEM formatted file that countains your private key
318 certfile - PEM formatted certificate chain file
320 See the methods of the parent class POP3 for more documentation.
323 def __init__(self
, host
, port
= POP3_SSL_PORT
, keyfile
= None, certfile
= None):
326 self
.keyfile
= keyfile
327 self
.certfile
= certfile
329 msg
= "getaddrinfo returns an empty list"
331 for res
in socket
.getaddrinfo(self
.host
, self
.port
, 0, socket
.SOCK_STREAM
):
332 af
, socktype
, proto
, canonname
, sa
= res
334 self
.sock
= socket
.socket(af
, socktype
, proto
)
335 self
.sock
.connect(sa
)
336 except socket
.error
, msg
:
343 raise socket
.error
, msg
344 self
.file = self
.sock
.makefile('rb')
345 self
.sslobj
= socket
.ssl(self
.sock
, self
.keyfile
, self
.certfile
)
347 self
.welcome
= self
._getresp
()
349 def _fillBuffer(self
):
350 localbuf
= self
.sslobj
.read()
351 if len(localbuf
) == 0:
352 raise error_proto('-ERR EOF')
353 self
.buffer += localbuf
357 renewline
= re
.compile(r
'.*?\n')
358 match
= renewline
.match(self
.buffer)
361 match
= renewline
.match(self
.buffer)
362 line
= match
.group(0)
363 self
.buffer = renewline
.sub('' ,self
.buffer, 1)
364 if self
._debugging
> 1: print '*get*', repr(line
)
367 if line
[-2:] == CRLF
:
368 return line
[:-2], octets
370 return line
[1:-1], octets
371 return line
[:-1], octets
373 def _putline(self
, line
):
374 if self
._debugging
> 1: print '*put*', repr(line
)
378 sent
= self
.sslobj
.write(line
)
385 """Signoff: commit changes on server, unlock mailbox, close connection."""
387 resp
= self
._shortcmd
('QUIT')
388 except error_proto
, val
:
391 del self
.sslobj
, self
.sock
395 if __name__
== "__main__":
397 a
= POP3(sys
.argv
[1])
402 (numMsgs
, totalSize
) = a
.stat()
403 for i
in range(1, numMsgs
+ 1):
404 (header
, msg
, octets
) = a
.retr(i
)
405 print "Message %d:" % i
408 print '-----------------------'