poplib.py revision 1b1c347311edcddac3381b6c2cfe2f86e82e2d60
1"""A POP3 client class. 2 3Based on the J. Myers POP3 draft, Jan. 96 4""" 5 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 11 12# Example (see the test function at the end of this file) 13 14# Imports 15 16import re, socket 17 18__all__ = ["POP3","error_proto","POP3_SSL"] 19 20# Exception raised when an error or invalid response is received: 21 22class error_proto(Exception): pass 23 24# Standard Port 25POP3_PORT = 110 26 27# POP SSL PORT 28POP3_SSL_PORT = 995 29 30# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF) 31CR = '\r' 32LF = '\n' 33CRLF = CR+LF 34 35 36class POP3: 37 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. 41 42 Minimal Command Set: 43 USER name user(name) 44 PASS string pass_(string) 45 STAT stat() 46 LIST [msg] list(msg = None) 47 RETR msg retr(msg) 48 DELE msg dele(msg) 49 NOOP noop() 50 RSET rset() 51 QUIT quit() 52 53 Optional Commands (some servers support these): 54 RPOP name rpop(name) 55 APOP name digest apop(name, digest) 56 TOP msg n top(msg, n) 57 UIDL [msg] uidl(msg = None) 58 59 Raises one exception: 'error_proto'. 60 61 Instantiate with: 62 POP3(hostname, port=110) 63 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 67 mailbox. 68 69 POP is a line-based protocol, which means large mail 70 messages consume lots of python cycles reading them 71 line-by-line. 72 73 If it's available on your mail server, use IMAP4 74 instead, it doesn't suffer from the two problems 75 above. 76 """ 77 78 79 def __init__(self, host, port=POP3_PORT, timeout=None): 80 self.host = host 81 self.port = port 82 self.sock = socket.create_connection((host, port), timeout) 83 self.file = self.sock.makefile('rb') 84 self._debugging = 0 85 self.welcome = self._getresp() 86 87 88 def _putline(self, line): 89 if self._debugging > 1: print '*put*', repr(line) 90 self.sock.sendall('%s%s' % (line, CRLF)) 91 92 93 # Internal: send one command to the server (through _putline()) 94 95 def _putcmd(self, line): 96 if self._debugging: print '*cmd*', repr(line) 97 self._putline(line) 98 99 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. 103 104 def _getline(self): 105 line = self.file.readline() 106 if self._debugging > 1: print '*get*', repr(line) 107 if not line: raise error_proto('-ERR EOF') 108 octets = len(line) 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 114 if line[0] == CR: 115 return line[1:-1], octets 116 return line[:-1], octets 117 118 119 # Internal: get a response from the server. 120 # Raise 'error_proto' if the response doesn't start with '+'. 121 122 def _getresp(self): 123 resp, o = self._getline() 124 if self._debugging > 1: print '*resp*', repr(resp) 125 c = resp[:1] 126 if c != '+': 127 raise error_proto(resp) 128 return resp 129 130 131 # Internal: get a response plus following text from the server. 132 133 def _getlongresp(self): 134 resp = self._getresp() 135 list = []; octets = 0 136 line, o = self._getline() 137 while line != '.': 138 if line[:2] == '..': 139 o = o-1 140 line = line[1:] 141 octets = octets + o 142 list.append(line) 143 line, o = self._getline() 144 return resp, list, octets 145 146 147 # Internal: send a command and get the response 148 149 def _shortcmd(self, line): 150 self._putcmd(line) 151 return self._getresp() 152 153 154 # Internal: send a command and get the response plus following text 155 156 def _longcmd(self, line): 157 self._putcmd(line) 158 return self._getlongresp() 159 160 161 # These can be useful: 162 163 def getwelcome(self): 164 return self.welcome 165 166 167 def set_debuglevel(self, level): 168 self._debugging = level 169 170 171 # Here are all the POP commands: 172 173 def user(self, user): 174 """Send user name, return response 175 176 (should indicate password required). 177 """ 178 return self._shortcmd('USER %s' % user) 179 180 181 def pass_(self, pswd): 182 """Send password, return response 183 184 (response includes message count, mailbox size). 185 186 NB: mailbox is locked by server from here to 'quit()' 187 """ 188 return self._shortcmd('PASS %s' % pswd) 189 190 191 def stat(self): 192 """Get mailbox status. 193 194 Result is tuple of 2 ints (message count, mailbox size) 195 """ 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) 202 203 204 def list(self, which=None): 205 """Request listing, return result. 206 207 Result without a message number argument is in form 208 ['response', ['mesg_num octets', ...], octets]. 209 210 Result when a message number argument is given is a 211 single response: the "scan listing" for that message. 212 """ 213 if which is not None: 214 return self._shortcmd('LIST %s' % which) 215 return self._longcmd('LIST') 216 217 218 def retr(self, which): 219 """Retrieve whole message number 'which'. 220 221 Result is in form ['response', ['line', ...], octets]. 222 """ 223 return self._longcmd('RETR %s' % which) 224 225 226 def dele(self, which): 227 """Delete message number 'which'. 228 229 Result is 'response'. 230 """ 231 return self._shortcmd('DELE %s' % which) 232 233 234 def noop(self): 235 """Does nothing. 236 237 One supposes the response indicates the server is alive. 238 """ 239 return self._shortcmd('NOOP') 240 241 242 def rset(self): 243 """Not sure what this does.""" 244 return self._shortcmd('RSET') 245 246 247 def quit(self): 248 """Signoff: commit changes on server, unlock mailbox, close connection.""" 249 try: 250 resp = self._shortcmd('QUIT') 251 except error_proto, val: 252 resp = val 253 self.file.close() 254 self.sock.close() 255 del self.file, self.sock 256 return resp 257 258 #__del__ = quit 259 260 261 # optional commands: 262 263 def rpop(self, user): 264 """Not sure what this does.""" 265 return self._shortcmd('RPOP %s' % user) 266 267 268 timestamp = re.compile(r'\+OK.*(<[^>]+>)') 269 270 def apop(self, user, secret): 271 """Authorisation 272 273 - only possible if server has supplied a timestamp in initial greeting. 274 275 Args: 276 user - mailbox user; 277 secret - secret shared between client and server. 278 279 NB: mailbox is locked by server from here to 'quit()' 280 """ 281 m = self.timestamp.match(self.welcome) 282 if not m: 283 raise error_proto('-ERR APOP not supported by server') 284 import hashlib 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)) 288 289 290 def top(self, which, howmuch): 291 """Retrieve message header of message number 'which' 292 and first 'howmuch' lines of message body. 293 294 Result is in form ['response', ['line', ...], octets]. 295 """ 296 return self._longcmd('TOP %s %s' % (which, howmuch)) 297 298 299 def uidl(self, which=None): 300 """Return message digest (unique id) list. 301 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] 305 """ 306 if which is not None: 307 return self._shortcmd('UIDL %s' % which) 308 return self._longcmd('UIDL') 309 310class POP3_SSL(POP3): 311 """POP3 client class over SSL connection 312 313 Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None) 314 315 hostname - the hostname of the pop3 over ssl server 316 port - port number 317 keyfile - PEM formatted file that countains your private key 318 certfile - PEM formatted certificate chain file 319 320 See the methods of the parent class POP3 for more documentation. 321 """ 322 323 def __init__(self, host, port = POP3_SSL_PORT, keyfile = None, certfile = None): 324 self.host = host 325 self.port = port 326 self.keyfile = keyfile 327 self.certfile = certfile 328 self.buffer = "" 329 msg = "getaddrinfo returns an empty list" 330 self.sock = None 331 for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): 332 af, socktype, proto, canonname, sa = res 333 try: 334 self.sock = socket.socket(af, socktype, proto) 335 self.sock.connect(sa) 336 except socket.error, msg: 337 if self.sock: 338 self.sock.close() 339 self.sock = None 340 continue 341 break 342 if not self.sock: 343 raise socket.error, msg 344 self.file = self.sock.makefile('rb') 345 self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile) 346 self._debugging = 0 347 self.welcome = self._getresp() 348 349 def _fillBuffer(self): 350 localbuf = self.sslobj.read() 351 if len(localbuf) == 0: 352 raise error_proto('-ERR EOF') 353 self.buffer += localbuf 354 355 def _getline(self): 356 line = "" 357 renewline = re.compile(r'.*?\n') 358 match = renewline.match(self.buffer) 359 while not match: 360 self._fillBuffer() 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) 365 366 octets = len(line) 367 if line[-2:] == CRLF: 368 return line[:-2], octets 369 if line[0] == CR: 370 return line[1:-1], octets 371 return line[:-1], octets 372 373 def _putline(self, line): 374 if self._debugging > 1: print '*put*', repr(line) 375 line += CRLF 376 bytes = len(line) 377 while bytes > 0: 378 sent = self.sslobj.write(line) 379 if sent == bytes: 380 break # avoid copy 381 line = line[sent:] 382 bytes = bytes - sent 383 384 def quit(self): 385 """Signoff: commit changes on server, unlock mailbox, close connection.""" 386 try: 387 resp = self._shortcmd('QUIT') 388 except error_proto, val: 389 resp = val 390 self.sock.close() 391 del self.sslobj, self.sock 392 return resp 393 394 395if __name__ == "__main__": 396 import sys 397 a = POP3(sys.argv[1]) 398 print a.getwelcome() 399 a.user(sys.argv[2]) 400 a.pass_(sys.argv[3]) 401 a.list() 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 406 for line in msg: 407 print ' ' + line 408 print '-----------------------' 409 a.quit() 410