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"]
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')
316 class POP3_SSL(POP3
):
317 """POP3 client class over SSL connection
319 Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None)
321 hostname - the hostname of the pop3 over ssl server
323 keyfile - PEM formatted file that countains your private key
324 certfile - PEM formatted certificate chain file
326 See the methods of the parent class POP3 for more documentation.
329 def __init__(self
, host
, port
= POP3_SSL_PORT
, keyfile
= None, certfile
= None):
332 self
.keyfile
= keyfile
333 self
.certfile
= certfile
335 msg
= "getaddrinfo returns an empty list"
337 for res
in socket
.getaddrinfo(self
.host
, self
.port
, 0, socket
.SOCK_STREAM
):
338 af
, socktype
, proto
, canonname
, sa
= res
340 self
.sock
= socket
.socket(af
, socktype
, proto
)
341 self
.sock
.connect(sa
)
342 except socket
.error
, msg
:
349 raise socket
.error
, msg
350 self
.file = self
.sock
.makefile('rb')
351 self
.sslobj
= ssl
.wrap_socket(self
.sock
, self
.keyfile
, self
.certfile
)
353 self
.welcome
= self
._getresp
()
355 def _fillBuffer(self
):
356 localbuf
= self
.sslobj
.read()
357 if len(localbuf
) == 0:
358 raise error_proto('-ERR EOF')
359 self
.buffer += localbuf
363 renewline
= re
.compile(r
'.*?\n')
364 match
= renewline
.match(self
.buffer)
367 match
= renewline
.match(self
.buffer)
368 line
= match
.group(0)
369 self
.buffer = renewline
.sub('' ,self
.buffer, 1)
370 if self
._debugging
> 1: print '*get*', repr(line
)
373 if line
[-2:] == CRLF
:
374 return line
[:-2], octets
376 return line
[1:-1], octets
377 return line
[:-1], octets
379 def _putline(self
, line
):
380 if self
._debugging
> 1: print '*put*', repr(line
)
384 sent
= self
.sslobj
.write(line
)
391 """Signoff: commit changes on server, unlock mailbox, close connection."""
393 resp
= self
._shortcmd
('QUIT')
394 except error_proto
, val
:
397 del self
.sslobj
, self
.sock
400 __all__
.append("POP3_SSL")
402 if __name__
== "__main__":
404 a
= POP3(sys
.argv
[1])
409 (numMsgs
, totalSize
) = a
.stat()
410 for i
in range(1, numMsgs
+ 1):
411 (header
, msg
, octets
) = a
.retr(i
)
412 print "Message %d:" % i
415 print '-----------------------'