Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(1)

Delta Between Two Patch Sets: Lib/imaplib.py

Issue 4972: context managerment support in imaplib, smtplib, ftplib
Left Patch Set: Created 7 years, 4 months ago
Right Patch Set: Created 5 years, 10 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « Doc/whatsnew/3.5.rst ('k') | Lib/test/test_imaplib.py » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 """IMAP4 client. 1 """IMAP4 client.
2 2
3 Based on RFC 2060. 3 Based on RFC 2060.
4 4
5 Public class: IMAP4 5 Public class: IMAP4
6 Public variable: Debug 6 Public variable: Debug
7 Public functions: Internaldate2tuple 7 Public functions: Internaldate2tuple
8 Int2AP 8 Int2AP
9 ParseFlags 9 ParseFlags
10 Time2Internaldate 10 Time2Internaldate
11 """ 11 """
12 12
13 # Author: Piers Lauder <piers@cs.su.oz.au> December 1997. 13 # Author: Piers Lauder <piers@cs.su.oz.au> December 1997.
14 # 14 #
15 # Authentication code contributed by Donn Cave <donn@u.washington.edu> June 1998 . 15 # Authentication code contributed by Donn Cave <donn@u.washington.edu> June 1998 .
16 # String method conversion by ESR, February 2001. 16 # String method conversion by ESR, February 2001.
17 # GET/SETACL contributed by Anthony Baxter <anthony@interlink.com.au> April 2001 . 17 # GET/SETACL contributed by Anthony Baxter <anthony@interlink.com.au> April 2001 .
18 # IMAP4_SSL contributed by Tino Lange <Tino.Lange@isg.de> March 2002. 18 # IMAP4_SSL contributed by Tino Lange <Tino.Lange@isg.de> March 2002.
19 # GET/SETQUOTA contributed by Andreas Zeidler <az@kreativkombinat.de> June 2002. 19 # GET/SETQUOTA contributed by Andreas Zeidler <az@kreativkombinat.de> June 2002.
20 # PROXYAUTH contributed by Rick Holbert <holbert.13@osu.edu> November 2002. 20 # PROXYAUTH contributed by Rick Holbert <holbert.13@osu.edu> November 2002.
21 # GET/SETANNOTATION contributed by Tomas Lindroos <skitta@abo.fi> June 2005. 21 # GET/SETANNOTATION contributed by Tomas Lindroos <skitta@abo.fi> June 2005.
22 22
23 __version__ = "2.58" 23 __version__ = "2.58"
24 24
25 import binascii, errno, random, re, socket, subprocess, sys, time, calendar 25 import binascii, errno, random, re, socket, subprocess, sys, time, calendar
26 from datetime import datetime, timezone, timedelta 26 from datetime import datetime, timezone, timedelta
27 from io import DEFAULT_BUFFER_SIZE
28
27 try: 29 try:
28 import ssl 30 import ssl
29 HAVE_SSL = True 31 HAVE_SSL = True
30 except ImportError: 32 except ImportError:
31 HAVE_SSL = False 33 HAVE_SSL = False
32 34
33 __all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple", 35 __all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
34 "Int2AP", "ParseFlags", "Time2Internaldate"] 36 "Int2AP", "ParseFlags", "Time2Internaldate"]
35 37
36 # Globals 38 # Globals
37 39
38 CRLF = b'\r\n' 40 CRLF = b'\r\n'
39 Debug = 0 41 Debug = 0
40 IMAP4_PORT = 143 42 IMAP4_PORT = 143
41 IMAP4_SSL_PORT = 993 43 IMAP4_SSL_PORT = 993
42 AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first 44 AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
45
46 # Maximal line length when calling readline(). This is to prevent
47 # reading arbitrary length lines. RFC 3501 and 2060 (IMAP 4rev1)
48 # don't specify a line length. RFC 2683 however suggests limiting client
49 # command lines to 1000 octets and server command lines to 8000 octets.
50 # We have selected 10000 for some extra margin and since that is supposedly
51 # also what UW and Panda IMAP does.
52 _MAXLINE = 10000
53
43 54
44 # Commands 55 # Commands
45 56
46 Commands = { 57 Commands = {
47 # name valid states 58 # name valid states
48 'APPEND': ('AUTH', 'SELECTED'), 59 'APPEND': ('AUTH', 'SELECTED'),
49 'AUTHENTICATE': ('NONAUTH',), 60 'AUTHENTICATE': ('NONAUTH',),
50 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'), 61 'CAPABILITY': ('NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT'),
51 'CHECK': ('SELECTED',), 62 'CHECK': ('SELECTED',),
52 'CLOSE': ('SELECTED',), 63 'CLOSE': ('SELECTED',),
(...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after
221 raise self.error('server not IMAP4 compliant') 232 raise self.error('server not IMAP4 compliant')
222 233
223 234
224 def __getattr__(self, attr): 235 def __getattr__(self, attr):
225 # Allow UPPERCASE variants of IMAP4 command methods. 236 # Allow UPPERCASE variants of IMAP4 command methods.
226 if attr in Commands: 237 if attr in Commands:
227 return getattr(self, attr.lower()) 238 return getattr(self, attr.lower())
228 raise AttributeError("Unknown IMAP4 command: '%s'" % attr) 239 raise AttributeError("Unknown IMAP4 command: '%s'" % attr)
229 240
230 def __enter__(self): 241 def __enter__(self):
231 """Context management protocol. Returns self."""
232 return self 242 return self
233 243
234 def __exit__(self, *args): 244 def __exit__(self, *args):
235 """Context management protocol. 245 try:
236 logout() will close properly even if not logged in.""" 246 self.logout()
237 self.logout() 247 except OSError:
248 pass
249
238 250
239 # Overridable methods 251 # Overridable methods
240 252
241 253
242 def _create_socket(self): 254 def _create_socket(self):
243 return socket.create_connection((self.host, self.port)) 255 return socket.create_connection((self.host, self.port))
244 256
245 def open(self, host = '', port = IMAP4_PORT): 257 def open(self, host = '', port = IMAP4_PORT):
246 """Setup connection to remote server on "host:port" 258 """Setup connection to remote server on "host:port"
247 (default: localhost:standard IMAP4 port). 259 (default: localhost:standard IMAP4 port).
248 This connection will be used by the routines: 260 This connection will be used by the routines:
249 read, readline, send, shutdown. 261 read, readline, send, shutdown.
250 """ 262 """
251 self.host = host 263 self.host = host
252 self.port = port 264 self.port = port
253 self.sock = self._create_socket() 265 self.sock = self._create_socket()
254 self.file = self.sock.makefile('rb') 266 self.file = self.sock.makefile('rb')
255 267
256 268
257 def read(self, size): 269 def read(self, size):
258 """Read 'size' bytes from remote.""" 270 """Read 'size' bytes from remote."""
259 return self.file.read(size) 271 return self.file.read(size)
260 272
261 273
262 def readline(self): 274 def readline(self):
263 """Read line from remote.""" 275 """Read line from remote."""
264 return self.file.readline() 276 line = self.file.readline(_MAXLINE + 1)
277 if len(line) > _MAXLINE:
278 raise self.error("got more than %d bytes" % _MAXLINE)
279 return line
265 280
266 281
267 def send(self, data): 282 def send(self, data):
268 """Send data to remote.""" 283 """Send data to remote."""
269 self.sock.sendall(data) 284 self.sock.sendall(data)
270 285
271 286
272 def shutdown(self): 287 def shutdown(self):
273 """Close I/O established in "open".""" 288 """Close I/O established in "open"."""
274 self.file.close() 289 self.file.close()
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
352 """Authenticate command - requires response processing. 367 """Authenticate command - requires response processing.
353 368
354 'mechanism' specifies which authentication mechanism is to 369 'mechanism' specifies which authentication mechanism is to
355 be used - it must appear in <instance>.capabilities in the 370 be used - it must appear in <instance>.capabilities in the
356 form AUTH=<mechanism>. 371 form AUTH=<mechanism>.
357 372
358 'authobject' must be a callable object: 373 'authobject' must be a callable object:
359 374
360 data = authobject(response) 375 data = authobject(response)
361 376
362 It will be called to process server continuation responses. 377 It will be called to process server continuation responses; the
363 It should return data that will be encoded and sent to server. 378 response argument it is passed will be a bytes. It should return bytes
364 It should return None if the client abort response '*' should 379 data that will be base64 encoded and sent to the server. It should
365 be sent instead. 380 return None if the client abort response '*' should be sent instead.
366 """ 381 """
367 mech = mechanism.upper() 382 mech = mechanism.upper()
368 # XXX: shouldn't this code be removed, not commented out? 383 # XXX: shouldn't this code be removed, not commented out?
369 #cap = 'AUTH=%s' % mech 384 #cap = 'AUTH=%s' % mech
370 #if not cap in self.capabilities: # Let the server decide! 385 #if not cap in self.capabilities: # Let the server decide!
371 # raise self.error("Server doesn't allow %s authentication." % mech) 386 # raise self.error("Server doesn't allow %s authentication." % mech)
372 self.literal = _Authenticator(authobject).process 387 self.literal = _Authenticator(authobject).process
373 typ, dat = self._simple_command('AUTHENTICATE', mech) 388 typ, dat = self._simple_command('AUTHENTICATE', mech)
374 if typ != 'OK': 389 if typ != 'OK':
375 raise self.error(dat[-1]) 390 raise self.error(dat[-1])
(...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after
538 553
539 (typ, [data]) = <instance>.login_cram_md5(user, password) 554 (typ, [data]) = <instance>.login_cram_md5(user, password)
540 """ 555 """
541 self.user, self.password = user, password 556 self.user, self.password = user, password
542 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH) 557 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
543 558
544 559
545 def _CRAM_MD5_AUTH(self, challenge): 560 def _CRAM_MD5_AUTH(self, challenge):
546 """ Authobject to use with CRAM-MD5 authentication. """ 561 """ Authobject to use with CRAM-MD5 authentication. """
547 import hmac 562 import hmac
548 return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest() 563 pwd = (self.password.encode('ASCII') if isinstance(self.password, str)
564 else self.password)
565 return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()
549 566
550 567
551 def logout(self): 568 def logout(self):
552 """Shutdown connection to server. 569 """Shutdown connection to server.
553 570
554 (typ, [data]) = <instance>.logout() 571 (typ, [data]) = <instance>.logout()
555 572
556 Returns server 'BYE' response. 573 Returns server 'BYE' response.
557 """ 574 """
558 self.state = 'LOGOUT' 575 self.state = 'LOGOUT'
(...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after
726 def starttls(self, ssl_context=None): 743 def starttls(self, ssl_context=None):
727 name = 'STARTTLS' 744 name = 'STARTTLS'
728 if not HAVE_SSL: 745 if not HAVE_SSL:
729 raise self.error('SSL support missing') 746 raise self.error('SSL support missing')
730 if self._tls_established: 747 if self._tls_established:
731 raise self.abort('TLS session already established') 748 raise self.abort('TLS session already established')
732 if name not in self.capabilities: 749 if name not in self.capabilities:
733 raise self.abort('TLS not supported by server') 750 raise self.abort('TLS not supported by server')
734 # Generate a default SSL context if none was passed. 751 # Generate a default SSL context if none was passed.
735 if ssl_context is None: 752 if ssl_context is None:
736 ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) 753 ssl_context = ssl._create_stdlib_context()
737 # SSLv2 considered harmful.
738 ssl_context.options |= ssl.OP_NO_SSLv2
739 typ, dat = self._simple_command(name) 754 typ, dat = self._simple_command(name)
740 if typ == 'OK': 755 if typ == 'OK':
741 self.sock = ssl_context.wrap_socket(self.sock) 756 server_hostname = self.host if ssl.HAS_SNI else None
757 self.sock = ssl_context.wrap_socket(self.sock,
758 server_hostname=server_hostname)
742 self.file = self.sock.makefile('rb') 759 self.file = self.sock.makefile('rb')
743 self._tls_established = True 760 self._tls_established = True
744 self._get_capabilities() 761 self._get_capabilities()
745 else: 762 else:
746 raise self.error("Couldn't establish TLS session") 763 raise self.error("Couldn't establish TLS session")
747 return self._untagged_response(typ, dat, name) 764 return self._untagged_response(typ, dat, name)
748 765
749 766
750 def status(self, mailbox, names): 767 def status(self, mailbox, names):
751 """Request named status conditions for mailbox. 768 """Request named status conditions for mailbox.
(...skipping 295 matching lines...) Expand 10 before | Expand all | Expand 10 after
1047 1064
1048 1065
1049 def _get_tagged_response(self, tag): 1066 def _get_tagged_response(self, tag):
1050 1067
1051 while 1: 1068 while 1:
1052 result = self.tagged_commands[tag] 1069 result = self.tagged_commands[tag]
1053 if result is not None: 1070 if result is not None:
1054 del self.tagged_commands[tag] 1071 del self.tagged_commands[tag]
1055 return result 1072 return result
1056 1073
1074 # If we've seen a BYE at this point, the socket will be
1075 # closed, so report the BYE now.
1076
1077 self._check_bye()
1078
1057 # Some have reported "unexpected response" exceptions. 1079 # Some have reported "unexpected response" exceptions.
1058 # Note that ignoring them here causes loops. 1080 # Note that ignoring them here causes loops.
1059 # Instead, send me details of the unexpected response and 1081 # Instead, send me details of the unexpected response and
1060 # I'll update the code in `_get_response()'. 1082 # I'll update the code in `_get_response()'.
1061 1083
1062 try: 1084 try:
1063 self._get_response() 1085 self._get_response()
1064 except self.abort as val: 1086 except self.abort as val:
1065 if __debug__: 1087 if __debug__:
1066 if self.debug >= 1: 1088 if self.debug >= 1:
1067 self.print_log() 1089 self.print_log()
1068 raise 1090 raise
1069 1091
1070 1092
1071 def _get_line(self): 1093 def _get_line(self):
1072 1094
1073 line = self.readline() 1095 line = self.readline()
1074 if not line: 1096 if not line:
1075 raise self.abort('socket error: EOF') 1097 raise self.abort('socket error: EOF')
1076 1098
1077 # Protocol mandates all lines terminated by CRLF 1099 # Protocol mandates all lines terminated by CRLF
1078 if not line.endswith(b'\r\n'): 1100 if not line.endswith(b'\r\n'):
1079 raise self.abort('socket error: unterminated line') 1101 raise self.abort('socket error: unterminated line: %r' % line)
1080 1102
1081 line = line[:-2] 1103 line = line[:-2]
1082 if __debug__: 1104 if __debug__:
1083 if self.debug >= 4: 1105 if self.debug >= 4:
1084 self._mesg('< %r' % line) 1106 self._mesg('< %r' % line)
1085 else: 1107 else:
1086 self._log('< %r' % line) 1108 self._log('< %r' % line)
1087 return line 1109 return line
1088 1110
1089 1111
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
1194 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, certfile= None, ssl_context=None): 1216 def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, certfile= None, ssl_context=None):
1195 if ssl_context is not None and keyfile is not None: 1217 if ssl_context is not None and keyfile is not None:
1196 raise ValueError("ssl_context and keyfile arguments are mutually " 1218 raise ValueError("ssl_context and keyfile arguments are mutually "
1197 "exclusive") 1219 "exclusive")
1198 if ssl_context is not None and certfile is not None: 1220 if ssl_context is not None and certfile is not None:
1199 raise ValueError("ssl_context and certfile arguments are mutuall y " 1221 raise ValueError("ssl_context and certfile arguments are mutuall y "
1200 "exclusive") 1222 "exclusive")
1201 1223
1202 self.keyfile = keyfile 1224 self.keyfile = keyfile
1203 self.certfile = certfile 1225 self.certfile = certfile
1226 if ssl_context is None:
1227 ssl_context = ssl._create_stdlib_context(certfile=certfile,
1228 keyfile=keyfile)
1204 self.ssl_context = ssl_context 1229 self.ssl_context = ssl_context
1205 IMAP4.__init__(self, host, port) 1230 IMAP4.__init__(self, host, port)
1206 1231
1207 def _create_socket(self): 1232 def _create_socket(self):
1208 sock = IMAP4._create_socket(self) 1233 sock = IMAP4._create_socket(self)
1209 if self.ssl_context: 1234 server_hostname = self.host if ssl.HAS_SNI else None
1210 return self.ssl_context.wrap_socket(sock) 1235 return self.ssl_context.wrap_socket(sock,
1211 else: 1236 server_hostname=server_hostname)
1212 return ssl.wrap_socket(sock, self.keyfile, self.certfile)
1213 1237
1214 def open(self, host='', port=IMAP4_SSL_PORT): 1238 def open(self, host='', port=IMAP4_SSL_PORT):
1215 """Setup connection to remote server on "host:port". 1239 """Setup connection to remote server on "host:port".
1216 (default: localhost:standard IMAP4 SSL port). 1240 (default: localhost:standard IMAP4 SSL port).
1217 This connection will be used by the routines: 1241 This connection will be used by the routines:
1218 read, readline, send, shutdown. 1242 read, readline, send, shutdown.
1219 """ 1243 """
1220 IMAP4.open(self, host, port) 1244 IMAP4.open(self, host, port)
1221 1245
1222 __all__.append("IMAP4_SSL") 1246 __all__.append("IMAP4_SSL")
(...skipping 19 matching lines...) Expand all
1242 def open(self, host = None, port = None): 1266 def open(self, host = None, port = None):
1243 """Setup a stream connection. 1267 """Setup a stream connection.
1244 This connection will be used by the routines: 1268 This connection will be used by the routines:
1245 read, readline, send, shutdown. 1269 read, readline, send, shutdown.
1246 """ 1270 """
1247 self.host = None # For compatibility with parent class 1271 self.host = None # For compatibility with parent class
1248 self.port = None 1272 self.port = None
1249 self.sock = None 1273 self.sock = None
1250 self.file = None 1274 self.file = None
1251 self.process = subprocess.Popen(self.command, 1275 self.process = subprocess.Popen(self.command,
1276 bufsize=DEFAULT_BUFFER_SIZE,
1252 stdin=subprocess.PIPE, stdout=subprocess.PIPE, 1277 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1253 shell=True, close_fds=True) 1278 shell=True, close_fds=True)
1254 self.writefile = self.process.stdin 1279 self.writefile = self.process.stdin
1255 self.readfile = self.process.stdout 1280 self.readfile = self.process.stdout
1256 1281
1257 def read(self, size): 1282 def read(self, size):
1258 """Read 'size' bytes from remote.""" 1283 """Read 'size' bytes from remote."""
1259 return self.readfile.read(size) 1284 return self.readfile.read(size)
1260 1285
1261 1286
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
1295 1320
1296 def encode(self, inp): 1321 def encode(self, inp):
1297 # 1322 #
1298 # Invoke binascii.b2a_base64 iteratively with 1323 # Invoke binascii.b2a_base64 iteratively with
1299 # short even length buffers, strip the trailing 1324 # short even length buffers, strip the trailing
1300 # line feed from the result and append. "Even" 1325 # line feed from the result and append. "Even"
1301 # means a number that factors to both 6 and 8, 1326 # means a number that factors to both 6 and 8,
1302 # so when it gets to the end of the 8-bit input 1327 # so when it gets to the end of the 8-bit input
1303 # there's no partial 6-bit output. 1328 # there's no partial 6-bit output.
1304 # 1329 #
1305 oup = '' 1330 oup = b''
1331 if isinstance(inp, str):
1332 inp = inp.encode('ASCII')
1306 while inp: 1333 while inp:
1307 if len(inp) > 48: 1334 if len(inp) > 48:
1308 t = inp[:48] 1335 t = inp[:48]
1309 inp = inp[48:] 1336 inp = inp[48:]
1310 else: 1337 else:
1311 t = inp 1338 t = inp
1312 inp = '' 1339 inp = b''
1313 e = binascii.b2a_base64(t) 1340 e = binascii.b2a_base64(t)
1314 if e: 1341 if e:
1315 oup = oup + e[:-1] 1342 oup = oup + e[:-1]
1316 return oup 1343 return oup
1317 1344
1318 def decode(self, inp): 1345 def decode(self, inp):
1319 if not inp: 1346 if not inp:
1320 return '' 1347 return b''
1321 return binascii.a2b_base64(inp) 1348 return binascii.a2b_base64(inp)
1322 1349
1323 Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ') 1350 Months = ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')
1324 Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])} 1351 Mon2num = {s.encode():n+1 for n, s in enumerate(Months[1:])}
1325 1352
1326 def Internaldate2tuple(resp): 1353 def Internaldate2tuple(resp):
1327 """Parse an IMAP4 INTERNALDATE string. 1354 """Parse an IMAP4 INTERNALDATE string.
1328 1355
1329 Return corresponding local time. The return value is a 1356 Return corresponding local time. The return value is a
1330 time.struct_time tuple or None if the string has wrong format. 1357 time.struct_time tuple or None if the string has wrong format.
(...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after
1521 except: 1548 except:
1522 print('\nTests failed.') 1549 print('\nTests failed.')
1523 1550
1524 if not Debug: 1551 if not Debug:
1525 print(''' 1552 print('''
1526 If you would like to see debugging output, 1553 If you would like to see debugging output,
1527 try: %s -d5 1554 try: %s -d5
1528 ''' % sys.argv[0]) 1555 ''' % sys.argv[0])
1529 1556
1530 raise 1557 raise
LEFTRIGHT

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+