poplib.py revision 2ad2569c721c6179667a16d1a6f2df4f93606636
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 11# Example (see the test function at the end of this file) 12 13# Imports 14 15import re, socket 16 17__all__ = ["POP3","error_proto"] 18 19# Exception raised when an error or invalid response is received: 20 21class error_proto(Exception): pass 22 23# Standard Port 24POP3_PORT = 110 25 26# Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF) 27CR = '\r' 28LF = '\n' 29CRLF = CR+LF 30 31 32class POP3: 33 34 """This class supports both the minimal and optional command sets. 35 Arguments can be strings or integers (where appropriate) 36 (e.g.: retr(1) and retr('1') both work equally well. 37 38 Minimal Command Set: 39 USER name user(name) 40 PASS string pass_(string) 41 STAT stat() 42 LIST [msg] list(msg = None) 43 RETR msg retr(msg) 44 DELE msg dele(msg) 45 NOOP noop() 46 RSET rset() 47 QUIT quit() 48 49 Optional Commands (some servers support these): 50 RPOP name rpop(name) 51 APOP name digest apop(name, digest) 52 TOP msg n top(msg, n) 53 UIDL [msg] uidl(msg = None) 54 55 Raises one exception: 'error_proto'. 56 57 Instantiate with: 58 POP3(hostname, port=110) 59 60 NB: the POP protocol locks the mailbox from user 61 authorization until QUIT, so be sure to get in, suck 62 the messages, and quit, each time you access the 63 mailbox. 64 65 POP is a line-based protocol, which means large mail 66 messages consume lots of python cycles reading them 67 line-by-line. 68 69 If it's available on your mail server, use IMAP4 70 instead, it doesn't suffer from the two problems 71 above. 72 """ 73 74 75 def __init__(self, host, port = POP3_PORT): 76 self.host = host 77 self.port = port 78 msg = "getaddrinfo returns an empty list" 79 for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): 80 af, socktype, proto, canonname, sa = res 81 try: 82 self.sock = socket.socket(af, socktype, proto) 83 self.sock.connect(sa) 84 except socket.error, msg: 85 self.sock.close() 86 self.sock = None 87 continue 88 break 89 if not self.sock: 90 raise socket.error, msg 91 self.file = self.sock.makefile('rb') 92 self._debugging = 0 93 self.welcome = self._getresp() 94 95 96 def _putline(self, line): 97 #if self._debugging > 1: print '*put*', `line` 98 self.sock.send('%s%s' % (line, CRLF)) 99 100 101 # Internal: send one command to the server (through _putline()) 102 103 def _putcmd(self, line): 104 #if self._debugging: print '*cmd*', `line` 105 self._putline(line) 106 107 108 # Internal: return one line from the server, stripping CRLF. 109 # This is where all the CPU time of this module is consumed. 110 # Raise error_proto('-ERR EOF') if the connection is closed. 111 112 def _getline(self): 113 line = self.file.readline() 114 #if self._debugging > 1: print '*get*', `line` 115 if not line: raise error_proto('-ERR EOF') 116 octets = len(line) 117 # server can send any combination of CR & LF 118 # however, 'readline()' returns lines ending in LF 119 # so only possibilities are ...LF, ...CRLF, CR...LF 120 if line[-2:] == CRLF: 121 return line[:-2], octets 122 if line[0] == CR: 123 return line[1:-1], octets 124 return line[:-1], octets 125 126 127 # Internal: get a response from the server. 128 # Raise 'error_proto' if the response doesn't start with '+'. 129 130 def _getresp(self): 131 resp, o = self._getline() 132 #if self._debugging > 1: print '*resp*', `resp` 133 c = resp[:1] 134 if c != '+': 135 raise error_proto(resp) 136 return resp 137 138 139 # Internal: get a response plus following text from the server. 140 141 def _getlongresp(self): 142 resp = self._getresp() 143 list = []; octets = 0 144 line, o = self._getline() 145 while line != '.': 146 if line[:2] == '..': 147 o = o-1 148 line = line[1:] 149 octets = octets + o 150 list.append(line) 151 line, o = self._getline() 152 return resp, list, octets 153 154 155 # Internal: send a command and get the response 156 157 def _shortcmd(self, line): 158 self._putcmd(line) 159 return self._getresp() 160 161 162 # Internal: send a command and get the response plus following text 163 164 def _longcmd(self, line): 165 self._putcmd(line) 166 return self._getlongresp() 167 168 169 # These can be useful: 170 171 def getwelcome(self): 172 return self.welcome 173 174 175 def set_debuglevel(self, level): 176 self._debugging = level 177 178 179 # Here are all the POP commands: 180 181 def user(self, user): 182 """Send user name, return response 183 184 (should indicate password required). 185 """ 186 return self._shortcmd('USER %s' % user) 187 188 189 def pass_(self, pswd): 190 """Send password, return response 191 192 (response includes message count, mailbox size). 193 194 NB: mailbox is locked by server from here to 'quit()' 195 """ 196 return self._shortcmd('PASS %s' % pswd) 197 198 199 def stat(self): 200 """Get mailbox status. 201 202 Result is tuple of 2 ints (message count, mailbox size) 203 """ 204 retval = self._shortcmd('STAT') 205 rets = retval.split() 206 #if self._debugging: print '*stat*', `rets` 207 numMessages = int(rets[1]) 208 sizeMessages = int(rets[2]) 209 return (numMessages, sizeMessages) 210 211 212 def list(self, which=None): 213 """Request listing, return result. 214 215 Result without a message number argument is in form 216 ['response', ['mesg_num octets', ...]]. 217 218 Result when a message number argument is given is a 219 single response: the "scan listing" for that message. 220 """ 221 if which: 222 return self._shortcmd('LIST %s' % which) 223 return self._longcmd('LIST') 224 225 226 def retr(self, which): 227 """Retrieve whole message number 'which'. 228 229 Result is in form ['response', ['line', ...], octets]. 230 """ 231 return self._longcmd('RETR %s' % which) 232 233 234 def dele(self, which): 235 """Delete message number 'which'. 236 237 Result is 'response'. 238 """ 239 return self._shortcmd('DELE %s' % which) 240 241 242 def noop(self): 243 """Does nothing. 244 245 One supposes the response indicates the server is alive. 246 """ 247 return self._shortcmd('NOOP') 248 249 250 def rset(self): 251 """Not sure what this does.""" 252 return self._shortcmd('RSET') 253 254 255 def quit(self): 256 """Signoff: commit changes on server, unlock mailbox, close connection.""" 257 try: 258 resp = self._shortcmd('QUIT') 259 except error_proto, val: 260 resp = val 261 self.file.close() 262 self.sock.close() 263 del self.file, self.sock 264 return resp 265 266 #__del__ = quit 267 268 269 # optional commands: 270 271 def rpop(self, user): 272 """Not sure what this does.""" 273 return self._shortcmd('RPOP %s' % user) 274 275 276 timestamp = re.compile(r'\+OK.*(<[^>]+>)') 277 278 def apop(self, user, secret): 279 """Authorisation 280 281 - only possible if server has supplied a timestamp in initial greeting. 282 283 Args: 284 user - mailbox user; 285 secret - secret shared between client and server. 286 287 NB: mailbox is locked by server from here to 'quit()' 288 """ 289 m = self.timestamp.match(self.welcome) 290 if not m: 291 raise error_proto('-ERR APOP not supported by server') 292 import md5 293 digest = md5.new(m.group(1)+secret).digest() 294 digest = ''.join(map(lambda x:'%02x'%ord(x), digest)) 295 return self._shortcmd('APOP %s %s' % (user, digest)) 296 297 298 def top(self, which, howmuch): 299 """Retrieve message header of message number 'which' 300 and first 'howmuch' lines of message body. 301 302 Result is in form ['response', ['line', ...], octets]. 303 """ 304 return self._longcmd('TOP %s %s' % (which, howmuch)) 305 306 307 def uidl(self, which=None): 308 """Return message digest (unique id) list. 309 310 If 'which', result contains unique id for that message 311 in the form 'response mesgnum uid', otherwise result is 312 the list ['response', ['mesgnum uid', ...], octets] 313 """ 314 if which: 315 return self._shortcmd('UIDL %s' % which) 316 return self._longcmd('UIDL') 317 318 319if __name__ == "__main__": 320 import sys 321 a = POP3(sys.argv[1]) 322 print a.getwelcome() 323 a.user(sys.argv[2]) 324 a.pass_(sys.argv[3]) 325 a.list() 326 (numMsgs, totalSize) = a.stat() 327 for i in range(1, numMsgs + 1): 328 (header, msg, octets) = a.retr(i) 329 print "Message ", `i`, ':' 330 for line in msg: 331 print ' ' + line 332 print '-----------------------' 333 a.quit() 334