0001 """A POP3 client class. 0002 0003 Based on the J. Myers POP3 draft, Jan. 96 0004 """ 0005 0006 # Author: David Ascher <david_ascher@brown.edu> 0007 # [heavily stealing from nntplib.py] 0008 # Updated: Piers Lauder <piers@cs.su.oz.au> [Jul '97] 0009 # String method conversion and test jig improvements by ESR, February 2001. 0010 # Added the POP3_SSL class. Methods loosely based on IMAP_SSL. Hector Urtubia <urtubia@mrbook.org> Aug 2003 0011 0012 # Example (see the test function at the end of this file) 0013 0014 # Imports 0015 0016 import re, socket 0017 0018 __all__ = ["POP3","error_proto","POP3_SSL"] 0019 0020 # Exception raised when an error or invalid response is received: 0021 0022 class error_proto(Exception): pass 0023 0024 # Standard Port 0025 POP3_PORT = 110 0026 0027 # POP SSL PORT 0028 POP3_SSL_PORT = 995 0029 0030 # Line terminators (we always output CRLF, but accept any of CRLF, LFCR, LF) 0031 CR = '\r' 0032 LF = '\n' 0033 CRLF = CR+LF 0034 0035 0036 class POP3: 0037 0038 """This class supports both the minimal and optional command sets. 0039 Arguments can be strings or integers (where appropriate) 0040 (e.g.: retr(1) and retr('1') both work equally well. 0041 0042 Minimal Command Set: 0043 USER name user(name) 0044 PASS string pass_(string) 0045 STAT stat() 0046 LIST [msg] list(msg = None) 0047 RETR msg retr(msg) 0048 DELE msg dele(msg) 0049 NOOP noop() 0050 RSET rset() 0051 QUIT quit() 0052 0053 Optional Commands (some servers support these): 0054 RPOP name rpop(name) 0055 APOP name digest apop(name, digest) 0056 TOP msg n top(msg, n) 0057 UIDL [msg] uidl(msg = None) 0058 0059 Raises one exception: 'error_proto'. 0060 0061 Instantiate with: 0062 POP3(hostname, port=110) 0063 0064 NB: the POP protocol locks the mailbox from user 0065 authorization until QUIT, so be sure to get in, suck 0066 the messages, and quit, each time you access the 0067 mailbox. 0068 0069 POP is a line-based protocol, which means large mail 0070 messages consume lots of python cycles reading them 0071 line-by-line. 0072 0073 If it's available on your mail server, use IMAP4 0074 instead, it doesn't suffer from the two problems 0075 above. 0076 """ 0077 0078 0079 def __init__(self, host, port = POP3_PORT): 0080 self.host = host 0081 self.port = port 0082 msg = "getaddrinfo returns an empty list" 0083 self.sock = None 0084 for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): 0085 af, socktype, proto, canonname, sa = res 0086 try: 0087 self.sock = socket.socket(af, socktype, proto) 0088 self.sock.connect(sa) 0089 except socket.error, msg: 0090 if self.sock: 0091 self.sock.close() 0092 self.sock = None 0093 continue 0094 break 0095 if not self.sock: 0096 raise socket.error, msg 0097 self.file = self.sock.makefile('rb') 0098 self._debugging = 0 0099 self.welcome = self._getresp() 0100 0101 0102 def _putline(self, line): 0103 if self._debugging > 1: print '*put*', repr(line) 0104 self.sock.sendall('%s%s' % (line, CRLF)) 0105 0106 0107 # Internal: send one command to the server (through _putline()) 0108 0109 def _putcmd(self, line): 0110 if self._debugging: print '*cmd*', repr(line) 0111 self._putline(line) 0112 0113 0114 # Internal: return one line from the server, stripping CRLF. 0115 # This is where all the CPU time of this module is consumed. 0116 # Raise error_proto('-ERR EOF') if the connection is closed. 0117 0118 def _getline(self): 0119 line = self.file.readline() 0120 if self._debugging > 1: print '*get*', repr(line) 0121 if not line: raise error_proto('-ERR EOF') 0122 octets = len(line) 0123 # server can send any combination of CR & LF 0124 # however, 'readline()' returns lines ending in LF 0125 # so only possibilities are ...LF, ...CRLF, CR...LF 0126 if line[-2:] == CRLF: 0127 return line[:-2], octets 0128 if line[0] == CR: 0129 return line[1:-1], octets 0130 return line[:-1], octets 0131 0132 0133 # Internal: get a response from the server. 0134 # Raise 'error_proto' if the response doesn't start with '+'. 0135 0136 def _getresp(self): 0137 resp, o = self._getline() 0138 if self._debugging > 1: print '*resp*', repr(resp) 0139 c = resp[:1] 0140 if c != '+': 0141 raise error_proto(resp) 0142 return resp 0143 0144 0145 # Internal: get a response plus following text from the server. 0146 0147 def _getlongresp(self): 0148 resp = self._getresp() 0149 list = []; octets = 0 0150 line, o = self._getline() 0151 while line != '.': 0152 if line[:2] == '..': 0153 o = o-1 0154 line = line[1:] 0155 octets = octets + o 0156 list.append(line) 0157 line, o = self._getline() 0158 return resp, list, octets 0159 0160 0161 # Internal: send a command and get the response 0162 0163 def _shortcmd(self, line): 0164 self._putcmd(line) 0165 return self._getresp() 0166 0167 0168 # Internal: send a command and get the response plus following text 0169 0170 def _longcmd(self, line): 0171 self._putcmd(line) 0172 return self._getlongresp() 0173 0174 0175 # These can be useful: 0176 0177 def getwelcome(self): 0178 return self.welcome 0179 0180 0181 def set_debuglevel(self, level): 0182 self._debugging = level 0183 0184 0185 # Here are all the POP commands: 0186 0187 def user(self, user): 0188 """Send user name, return response 0189 0190 (should indicate password required). 0191 """ 0192 return self._shortcmd('USER %s' % user) 0193 0194 0195 def pass_(self, pswd): 0196 """Send password, return response 0197 0198 (response includes message count, mailbox size). 0199 0200 NB: mailbox is locked by server from here to 'quit()' 0201 """ 0202 return self._shortcmd('PASS %s' % pswd) 0203 0204 0205 def stat(self): 0206 """Get mailbox status. 0207 0208 Result is tuple of 2 ints (message count, mailbox size) 0209 """ 0210 retval = self._shortcmd('STAT') 0211 rets = retval.split() 0212 if self._debugging: print '*stat*', repr(rets) 0213 numMessages = int(rets[1]) 0214 sizeMessages = int(rets[2]) 0215 return (numMessages, sizeMessages) 0216 0217 0218 def list(self, which=None): 0219 """Request listing, return result. 0220 0221 Result without a message number argument is in form 0222 ['response', ['mesg_num octets', ...]]. 0223 0224 Result when a message number argument is given is a 0225 single response: the "scan listing" for that message. 0226 """ 0227 if which is not None: 0228 return self._shortcmd('LIST %s' % which) 0229 return self._longcmd('LIST') 0230 0231 0232 def retr(self, which): 0233 """Retrieve whole message number 'which'. 0234 0235 Result is in form ['response', ['line', ...], octets]. 0236 """ 0237 return self._longcmd('RETR %s' % which) 0238 0239 0240 def dele(self, which): 0241 """Delete message number 'which'. 0242 0243 Result is 'response'. 0244 """ 0245 return self._shortcmd('DELE %s' % which) 0246 0247 0248 def noop(self): 0249 """Does nothing. 0250 0251 One supposes the response indicates the server is alive. 0252 """ 0253 return self._shortcmd('NOOP') 0254 0255 0256 def rset(self): 0257 """Not sure what this does.""" 0258 return self._shortcmd('RSET') 0259 0260 0261 def quit(self): 0262 """Signoff: commit changes on server, unlock mailbox, close connection.""" 0263 try: 0264 resp = self._shortcmd('QUIT') 0265 except error_proto, val: 0266 resp = val 0267 self.file.close() 0268 self.sock.close() 0269 del self.file, self.sock 0270 return resp 0271 0272 #__del__ = quit 0273 0274 0275 # optional commands: 0276 0277 def rpop(self, user): 0278 """Not sure what this does.""" 0279 return self._shortcmd('RPOP %s' % user) 0280 0281 0282 timestamp = re.compile(r'\+OK.*(<[^>]+>)') 0283 0284 def apop(self, user, secret): 0285 """Authorisation 0286 0287 - only possible if server has supplied a timestamp in initial greeting. 0288 0289 Args: 0290 user - mailbox user; 0291 secret - secret shared between client and server. 0292 0293 NB: mailbox is locked by server from here to 'quit()' 0294 """ 0295 m = self.timestamp.match(self.welcome) 0296 if not m: 0297 raise error_proto('-ERR APOP not supported by server') 0298 import md5 0299 digest = md5.new(m.group(1)+secret).digest() 0300 digest = ''.join(map(lambda x:'%02x'%ord(x), digest)) 0301 return self._shortcmd('APOP %s %s' % (user, digest)) 0302 0303 0304 def top(self, which, howmuch): 0305 """Retrieve message header of message number 'which' 0306 and first 'howmuch' lines of message body. 0307 0308 Result is in form ['response', ['line', ...], octets]. 0309 """ 0310 return self._longcmd('TOP %s %s' % (which, howmuch)) 0311 0312 0313 def uidl(self, which=None): 0314 """Return message digest (unique id) list. 0315 0316 If 'which', result contains unique id for that message 0317 in the form 'response mesgnum uid', otherwise result is 0318 the list ['response', ['mesgnum uid', ...], octets] 0319 """ 0320 if which is not None: 0321 return self._shortcmd('UIDL %s' % which) 0322 return self._longcmd('UIDL') 0323 0324 class POP3_SSL(POP3): 0325 """POP3 client class over SSL connection 0326 0327 Instantiate with: POP3_SSL(hostname, port=995, keyfile=None, certfile=None) 0328 0329 hostname - the hostname of the pop3 over ssl server 0330 port - port number 0331 keyfile - PEM formatted file that countains your private key 0332 certfile - PEM formatted certificate chain file 0333 0334 See the methods of the parent class POP3 for more documentation. 0335 """ 0336 0337 def __init__(self, host, port = POP3_SSL_PORT, keyfile = None, certfile = None): 0338 self.host = host 0339 self.port = port 0340 self.keyfile = keyfile 0341 self.certfile = certfile 0342 self.buffer = "" 0343 msg = "getaddrinfo returns an empty list" 0344 self.sock = None 0345 for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): 0346 af, socktype, proto, canonname, sa = res 0347 try: 0348 self.sock = socket.socket(af, socktype, proto) 0349 self.sock.connect(sa) 0350 except socket.error, msg: 0351 if self.sock: 0352 self.sock.close() 0353 self.sock = None 0354 continue 0355 break 0356 if not self.sock: 0357 raise socket.error, msg 0358 self.file = self.sock.makefile('rb') 0359 self.sslobj = socket.ssl(self.sock, self.keyfile, self.certfile) 0360 self._debugging = 0 0361 self.welcome = self._getresp() 0362 0363 def _fillBuffer(self): 0364 localbuf = self.sslobj.read() 0365 if len(localbuf) == 0: 0366 raise error_proto('-ERR EOF') 0367 self.buffer += localbuf 0368 0369 def _getline(self): 0370 line = "" 0371 renewline = re.compile(r'.*?\n') 0372 match = renewline.match(self.buffer) 0373 while not match: 0374 self._fillBuffer() 0375 match = renewline.match(self.buffer) 0376 line = match.group(0) 0377 self.buffer = renewline.sub('' ,self.buffer, 1) 0378 if self._debugging > 1: print '*get*', repr(line) 0379 0380 octets = len(line) 0381 if line[-2:] == CRLF: 0382 return line[:-2], octets 0383 if line[0] == CR: 0384 return line[1:-1], octets 0385 return line[:-1], octets 0386 0387 def _putline(self, line): 0388 if self._debugging > 1: print '*put*', repr(line) 0389 line += CRLF 0390 bytes = len(line) 0391 while bytes > 0: 0392 sent = self.sslobj.write(line) 0393 if sent == bytes: 0394 break # avoid copy 0395 line = line[sent:] 0396 bytes = bytes - sent 0397 0398 def quit(self): 0399 """Signoff: commit changes on server, unlock mailbox, close connection.""" 0400 try: 0401 resp = self._shortcmd('QUIT') 0402 except error_proto, val: 0403 resp = val 0404 self.sock.close() 0405 del self.sslobj, self.sock 0406 return resp 0407 0408 0409 if __name__ == "__main__": 0410 import sys 0411 a = POP3(sys.argv[1]) 0412 print a.getwelcome() 0413 a.user(sys.argv[2]) 0414 a.pass_(sys.argv[3]) 0415 a.list() 0416 (numMsgs, totalSize) = a.stat() 0417 for i in range(1, numMsgs + 1): 0418 (header, msg, octets) = a.retr(i) 0419 print "Message %d:" % i 0420 for line in msg: 0421 print ' ' + line 0422 print '-----------------------' 0423 a.quit() 0424
Generated by PyXR 0.9.4