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
,
80 timeout
=socket
._GLOBAL
_DEFAULT
_TIMEOUT
):
83 self
.sock
= socket
.create_connection((host
, port
), timeout
)
84 self
.file = self
.sock
.makefile('rb')
86 self
.welcome
= self
._getresp
()
89 def _putline(self
, line
):
90 if self
._debugging
> 1: print '*put*', repr(line
)
91 self
.sock
.sendall('%s%s' % (line
, CRLF
))
94 # Internal: send one command to the server (through _putline())
96 def _putcmd(self
, line
):
97 if self
._debugging
: print '*cmd*', repr(line
)
101 # Internal: return one line from the server, stripping CRLF.
102 # This is where all the CPU time of this module is consumed.
103 # Raise error_proto('-ERR EOF') if the connection is closed.
106 line
= self
.file.readline()
107 if self
._debugging
> 1: print '*get*', repr(line
)
108 if not line
: raise error_proto('-ERR EOF')
110 # server can send any combination of CR & LF
111 # however, 'readline()' returns lines ending in LF
112 # so only possibilities are ...LF, ...CRLF, CR...LF
113 if line
[-2:] == CRLF
:
114 return line
[:-2], octets
116 return line
[1:-1], octets
117 return line
[:-1], octets
120 # Internal: get a response from the server.
121 # Raise 'error_proto' if the response doesn't start with '+'.
124 resp
, o
= self
._getline
()
125 if self
._debugging
> 1: print '*resp*', repr(resp
)
128 raise error_proto(resp
)
132 # Internal: get a response plus following text from the server.
134 def _getlongresp(self
):
135 resp
= self
._getresp
()
136 list = []; octets
= 0
137 line
, o
= self
._getline
()
144 line
, o
= self
._getline
()
145 return resp
, list, octets
148 # Internal: send a command and get the response
150 def _shortcmd(self
, line
):
152 return self
._getresp
()
155 # Internal: send a command and get the response plus following text
157 def _longcmd(self
, line
):
159 return self
._getlongresp
()
162 # These can be useful:
164 def getwelcome(self
):
168 def set_debuglevel(self
, level
):
169 self
._debugging
= level
172 # Here are all the POP commands:
174 def user(self
, user
):
175 """Send user name, return response
177 (should indicate password required).
179 return self
._shortcmd
('USER %s' % user
)
182 def pass_(self
, pswd
):
183 """Send password, return response
185 (response includes message count, mailbox size).
187 NB: mailbox is locked by server from here to 'quit()'
189 return self
._shortcmd
('PASS %s' % pswd
)
193 """Get mailbox status.
195 Result is tuple of 2 ints (message count, mailbox size)
197 retval
= self
._shortcmd
('STAT')
198 rets
= retval
.split()
199 if self
._debugging
: print '*stat*', repr(rets
)
200 numMessages
= int(rets
[1])
201 sizeMessages
= int(rets
[2])
202 return (numMessages
, sizeMessages
)
205 def list(self
, which
=None):
206 """Request listing, return result.
208 Result without a message number argument is in form
209 ['response', ['mesg_num octets', ...], octets].
211 Result when a message number argument is given is a
212 single response: the "scan listing" for that message.
214 if which
is not None:
215 return self
._shortcmd
('LIST %s' % which
)
216 return self
._longcmd
('LIST')
219 def retr(self
, which
):
220 """Retrieve whole message number 'which'.
222 Result is in form ['response', ['line', ...], octets].
224 return self
._longcmd
('RETR %s' % which
)
227 def dele(self
, which
):
228 """Delete message number 'which'.
230 Result is 'response'.
232 return self
._shortcmd
('DELE %s' % which
)
238 One supposes the response indicates the server is alive.
240 return self
._shortcmd
('NOOP')
244 """Unmark all messages marked for deletion."""
245 return self
._shortcmd
('RSET')
249 """Signoff: commit changes on server, unlock mailbox, close connection."""
251 resp
= self
._shortcmd
('QUIT')
252 except error_proto
, val
:
256 del self
.file, self
.sock
264 def rpop(self
, user
):
265 """Not sure what this does."""
266 return self
._shortcmd
('RPOP %s' % user
)
269 timestamp
= re
.compile(r
'\+OK.*(<[^>]+>)')
271 def apop(self
, user
, secret
):
274 - only possible if server has supplied a timestamp in initial greeting.
278 secret - secret shared between client and server.
280 NB: mailbox is locked by server from here to 'quit()'
282 m
= self
.timestamp
.match(self
.welcome
)
284 raise error_proto('-ERR APOP not supported by server')
286 digest
= hashlib
.md5(m
.group(1)+secret
).digest()
287 digest
= ''.join(map(lambda x
:'%02x'%ord(x
), digest
))
288 return self
._shortcmd
('APOP %s %s' % (user
, digest
))
291 def top(self
, which
, howmuch
):
292 """Retrieve message header of message number 'which'
293 and first 'howmuch' lines of message body.
295 Result is in form ['response', ['line', ...], octets].
297 return self
._longcmd
('TOP %s %s' % (which
, howmuch
))
300 def uidl(self
, which
=None):
301 """Return message digest (unique id) list.
303 If 'which', result contains unique id for that message
304 in the form 'response mesgnum uid', otherwise result is
305 the list ['response', ['mesgnum uid', ...], octets]
307 if which
is not None:
308 return self
._shortcmd
('UIDL %s' % which
)
309 return self
._longcmd
('UIDL')
317 class POP3_SSL(POP3
):
318 """POP3 client class over SSL connection
320 Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None)
322 hostname - the hostname of the pop3 over ssl server
324 keyfile - PEM formatted file that countains your private key
325 certfile - PEM formatted certificate chain file
327 See the methods of the parent class POP3 for more documentation.
330 def __init__(self
, host
, port
= POP3_SSL_PORT
, keyfile
= None, certfile
= None):
333 self
.keyfile
= keyfile
334 self
.certfile
= certfile
336 msg
= "getaddrinfo returns an empty list"
338 for res
in socket
.getaddrinfo(self
.host
, self
.port
, 0, socket
.SOCK_STREAM
):
339 af
, socktype
, proto
, canonname
, sa
= res
341 self
.sock
= socket
.socket(af
, socktype
, proto
)
342 self
.sock
.connect(sa
)
343 except socket
.error
, msg
:
350 raise socket
.error
, msg
351 self
.file = self
.sock
.makefile('rb')
352 self
.sslobj
= ssl
.wrap_socket(self
.sock
, self
.keyfile
, self
.certfile
)
354 self
.welcome
= self
._getresp
()
356 def _fillBuffer(self
):
357 localbuf
= self
.sslobj
.read()
358 if len(localbuf
) == 0:
359 raise error_proto('-ERR EOF')
360 self
.buffer += localbuf
364 renewline
= re
.compile(r
'.*?\n')
365 match
= renewline
.match(self
.buffer)
368 match
= renewline
.match(self
.buffer)
369 line
= match
.group(0)
370 self
.buffer = renewline
.sub('' ,self
.buffer, 1)
371 if self
._debugging
> 1: print '*get*', repr(line
)
374 if line
[-2:] == CRLF
:
375 return line
[:-2], octets
377 return line
[1:-1], octets
378 return line
[:-1], octets
380 def _putline(self
, line
):
381 if self
._debugging
> 1: print '*put*', repr(line
)
385 sent
= self
.sslobj
.write(line
)
392 """Signoff: commit changes on server, unlock mailbox, close connection."""
394 resp
= self
._shortcmd
('QUIT')
395 except error_proto
, val
:
398 del self
.sslobj
, self
.sock
401 __all__
.append("POP3_SSL")
403 if __name__
== "__main__":
405 a
= POP3(sys
.argv
[1])
410 (numMsgs
, totalSize
) = a
.stat()
411 for i
in range(1, numMsgs
+ 1):
412 (header
, msg
, octets
) = a
.retr(i
)
413 print "Message %d:" % i
416 print '-----------------------'