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