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

Delta Between Two Patch Sets: Lib/imaplib.py

Issue 4972: context managerment support in imaplib, smtplib, ftplib
Left Patch Set: Created 7 years ago
Right Patch Set: Created 5 years, 7 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.
236 logout() will close properly even if not logged in."""
237 try: 245 try:
238 self.logout() 246 self.logout()
239 except OSError: 247 except OSError:
240 pass 248 pass
241 249
242 250
243 # Overridable methods 251 # Overridable methods
244 252
245 253
246 def _create_socket(self): 254 def _create_socket(self):
(...skipping 11 matching lines...) Expand all
258 self.file = self.sock.makefile('rb') 266 self.file = self.sock.makefile('rb')
259 267
260 268
261 def read(self, size): 269 def read(self, size):
262 """Read 'size' bytes from remote.""" 270 """Read 'size' bytes from remote."""
263 return self.file.read(size) 271 return self.file.read(size)
264 272
265 273
266 def readline(self): 274 def readline(self):
267 """Read line from remote.""" 275 """Read line from remote."""
268 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
269 280
270 281
271 def send(self, data): 282 def send(self, data):
272 """Send data to remote.""" 283 """Send data to remote."""
273 self.sock.sendall(data) 284 self.sock.sendall(data)
274 285
275 286
276 def shutdown(self): 287 def shutdown(self):
277 """Close I/O established in "open".""" 288 """Close I/O established in "open"."""
278 self.file.close() 289 self.file.close()
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
356 """Authenticate command - requires response processing. 367 """Authenticate command - requires response processing.
357 368
358 'mechanism' specifies which authentication mechanism is to 369 'mechanism' specifies which authentication mechanism is to
359 be used - it must appear in <instance>.capabilities in the 370 be used - it must appear in <instance>.capabilities in the
360 form AUTH=<mechanism>. 371 form AUTH=<mechanism>.
361 372
362 'authobject' must be a callable object: 373 'authobject' must be a callable object:
363 374
364 data = authobject(response) 375 data = authobject(response)
365 376
366 It will be called to process server continuation responses. 377 It will be called to process server continuation responses; the
367 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
368 It should return None if the client abort response '*' should 379 data that will be base64 encoded and sent to the server. It should
369 be sent instead. 380 return None if the client abort response '*' should be sent instead.
370 """ 381 """
371 mech = mechanism.upper() 382 mech = mechanism.upper()
372 # XXX: shouldn't this code be removed, not commented out? 383 # XXX: shouldn't this code be removed, not commented out?
373 #cap = 'AUTH=%s' % mech 384 #cap = 'AUTH=%s' % mech
374 #if not cap in self.capabilities: # Let the server decide! 385 #if not cap in self.capabilities: # Let the server decide!
375 # raise self.error("Server doesn't allow %s authentication." % mech) 386 # raise self.error("Server doesn't allow %s authentication." % mech)
376 self.literal = _Authenticator(authobject).process 387 self.literal = _Authenticator(authobject).process
377 typ, dat = self._simple_command('AUTHENTICATE', mech) 388 typ, dat = self._simple_command('AUTHENTICATE', mech)
378 if typ != 'OK': 389 if typ != 'OK':
379 raise self.error(dat[-1]) 390 raise self.error(dat[-1])
(...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after
542 553
543 (typ, [data]) = <instance>.login_cram_md5(user, password) 554 (typ, [data]) = <instance>.login_cram_md5(user, password)
544 """ 555 """
545 self.user, self.password = user, password 556 self.user, self.password = user, password
546 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH) 557 return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH)
547 558
548 559
549 def _CRAM_MD5_AUTH(self, challenge): 560 def _CRAM_MD5_AUTH(self, challenge):
550 """ Authobject to use with CRAM-MD5 authentication. """ 561 """ Authobject to use with CRAM-MD5 authentication. """
551 import hmac 562 import hmac
552 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()
553 566
554 567
555 def logout(self): 568 def logout(self):
556 """Shutdown connection to server. 569 """Shutdown connection to server.
557 570
558 (typ, [data]) = <instance>.logout() 571 (typ, [data]) = <instance>.logout()
559 572
560 Returns server 'BYE' response. 573 Returns server 'BYE' response.
561 """ 574 """
562 self.state = 'LOGOUT' 575 self.state = 'LOGOUT'
(...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after
730 def starttls(self, ssl_context=None): 743 def starttls(self, ssl_context=None):
731 name = 'STARTTLS' 744 name = 'STARTTLS'
732 if not HAVE_SSL: 745 if not HAVE_SSL:
733 raise self.error('SSL support missing') 746 raise self.error('SSL support missing')
734 if self._tls_established: 747 if self._tls_established:
735 raise self.abort('TLS session already established') 748 raise self.abort('TLS session already established')
736 if name not in self.capabilities: 749 if name not in self.capabilities:
737 raise self.abort('TLS not supported by server') 750 raise self.abort('TLS not supported by server')
738 # Generate a default SSL context if none was passed. 751 # Generate a default SSL context if none was passed.
739 if ssl_context is None: 752 if ssl_context is None:
740 ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) 753 ssl_context = ssl._create_stdlib_context()
741 # SSLv2 considered harmful.
742 ssl_context.options |= ssl.OP_NO_SSLv2
743 typ, dat = self._simple_command(name) 754 typ, dat = self._simple_command(name)
744 if typ == 'OK': 755 if typ == 'OK':
745 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)
746 self.file = self.sock.makefile('rb') 759 self.file = self.sock.makefile('rb')
747 self._tls_established = True 760 self._tls_established = True
748 self._get_capabilities() 761 self._get_capabilities()
749 else: 762 else:
750 raise self.error("Couldn't establish TLS session") 763 raise self.error("Couldn't establish TLS session")
751 return self._untagged_response(typ, dat, name) 764 return self._untagged_response(typ, dat, name)
752 765
753 766
754 def status(self, mailbox, names): 767 def status(self, mailbox, names):
755 """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
1051 1064
1052 1065
1053 def _get_tagged_response(self, tag): 1066 def _get_tagged_response(self, tag):
1054 1067
1055 while 1: 1068 while 1:
1056 result = self.tagged_commands[tag] 1069 result = self.tagged_commands[tag]
1057 if result is not None: 1070 if result is not None:
1058 del self.tagged_commands[tag] 1071 del self.tagged_commands[tag]
1059 return result 1072 return result
1060 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
1061 # Some have reported "unexpected response" exceptions. 1079 # Some have reported "unexpected response" exceptions.
1062 # Note that ignoring them here causes loops. 1080 # Note that ignoring them here causes loops.
1063 # Instead, send me details of the unexpected response and 1081 # Instead, send me details of the unexpected response and
1064 # I'll update the code in `_get_response()'. 1082 # I'll update the code in `_get_response()'.
1065 1083
1066 try: 1084 try:
1067 self._get_response() 1085 self._get_response()
1068 except self.abort as val: 1086 except self.abort as val:
1069 if __debug__: 1087 if __debug__:
1070 if self.debug >= 1: 1088 if self.debug >= 1:
1071 self.print_log() 1089 self.print_log()
1072 raise 1090 raise
1073 1091
1074 1092
1075 def _get_line(self): 1093 def _get_line(self):
1076 1094
1077 line = self.readline() 1095 line = self.readline()
1078 if not line: 1096 if not line:
1079 raise self.abort('socket error: EOF') 1097 raise self.abort('socket error: EOF')
1080 1098
1081 # Protocol mandates all lines terminated by CRLF 1099 # Protocol mandates all lines terminated by CRLF
1082 if not line.endswith(b'\r\n'): 1100 if not line.endswith(b'\r\n'):
1083 raise self.abort('socket error: unterminated line') 1101 raise self.abort('socket error: unterminated line: %r' % line)
1084 1102
1085 line = line[:-2] 1103 line = line[:-2]
1086 if __debug__: 1104 if __debug__:
1087 if self.debug >= 4: 1105 if self.debug >= 4:
1088 self._mesg('< %r' % line) 1106 self._mesg('< %r' % line)
1089 else: 1107 else:
1090 self._log('< %r' % line) 1108 self._log('< %r' % line)
1091 return line 1109 return line
1092 1110
1093 1111
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
1198 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):
1199 if ssl_context is not None and keyfile is not None: 1217 if ssl_context is not None and keyfile is not None:
1200 raise ValueError("ssl_context and keyfile arguments are mutually " 1218 raise ValueError("ssl_context and keyfile arguments are mutually "
1201 "exclusive") 1219 "exclusive")
1202 if ssl_context is not None and certfile is not None: 1220 if ssl_context is not None and certfile is not None:
1203 raise ValueError("ssl_context and certfile arguments are mutuall y " 1221 raise ValueError("ssl_context and certfile arguments are mutuall y "
1204 "exclusive") 1222 "exclusive")
1205 1223
1206 self.keyfile = keyfile 1224 self.keyfile = keyfile
1207 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)
1208 self.ssl_context = ssl_context 1229 self.ssl_context = ssl_context
1209 IMAP4.__init__(self, host, port) 1230 IMAP4.__init__(self, host, port)
1210 1231
1211 def _create_socket(self): 1232 def _create_socket(self):
1212 sock = IMAP4._create_socket(self) 1233 sock = IMAP4._create_socket(self)
1213 if self.ssl_context: 1234 server_hostname = self.host if ssl.HAS_SNI else None
1214 return self.ssl_context.wrap_socket(sock) 1235 return self.ssl_context.wrap_socket(sock,
1215 else: 1236 server_hostname=server_hostname)
1216 return ssl.wrap_socket(sock, self.keyfile, self.certfile)
1217 1237
1218 def open(self, host='', port=IMAP4_SSL_PORT): 1238 def open(self, host='', port=IMAP4_SSL_PORT):
1219 """Setup connection to remote server on "host:port". 1239 """Setup connection to remote server on "host:port".
1220 (default: localhost:standard IMAP4 SSL port). 1240 (default: localhost:standard IMAP4 SSL port).
1221 This connection will be used by the routines: 1241 This connection will be used by the routines:
1222 read, readline, send, shutdown. 1242 read, readline, send, shutdown.
1223 """ 1243 """
1224 IMAP4.open(self, host, port) 1244 IMAP4.open(self, host, port)
1225 1245
1226 __all__.append("IMAP4_SSL") 1246 __all__.append("IMAP4_SSL")
(...skipping 19 matching lines...) Expand all
1246 def open(self, host = None, port = None): 1266 def open(self, host = None, port = None):
1247 """Setup a stream connection. 1267 """Setup a stream connection.
1248 This connection will be used by the routines: 1268 This connection will be used by the routines:
1249 read, readline, send, shutdown. 1269 read, readline, send, shutdown.
1250 """ 1270 """
1251 self.host = None # For compatibility with parent class 1271 self.host = None # For compatibility with parent class
1252 self.port = None 1272 self.port = None
1253 self.sock = None 1273 self.sock = None
1254 self.file = None 1274 self.file = None
1255 self.process = subprocess.Popen(self.command, 1275 self.process = subprocess.Popen(self.command,
1276 bufsize=DEFAULT_BUFFER_SIZE,
1256 stdin=subprocess.PIPE, stdout=subprocess.PIPE, 1277 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1257 shell=True, close_fds=True) 1278 shell=True, close_fds=True)
1258 self.writefile = self.process.stdin 1279 self.writefile = self.process.stdin
1259 self.readfile = self.process.stdout 1280 self.readfile = self.process.stdout
1260 1281
1261 def read(self, size): 1282 def read(self, size):
1262 """Read 'size' bytes from remote.""" 1283 """Read 'size' bytes from remote."""
1263 return self.readfile.read(size) 1284 return self.readfile.read(size)
1264 1285
1265 1286
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
1299 1320
1300 def encode(self, inp): 1321 def encode(self, inp):
1301 # 1322 #
1302 # Invoke binascii.b2a_base64 iteratively with 1323 # Invoke binascii.b2a_base64 iteratively with
1303 # short even length buffers, strip the trailing 1324 # short even length buffers, strip the trailing
1304 # line feed from the result and append. "Even" 1325 # line feed from the result and append. "Even"
1305 # means a number that factors to both 6 and 8, 1326 # means a number that factors to both 6 and 8,
1306 # 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
1307 # there's no partial 6-bit output. 1328 # there's no partial 6-bit output.
1308 # 1329 #
1309 oup = '' 1330 oup = b''
1331 if isinstance(inp, str):
1332 inp = inp.encode('ASCII')
1310 while inp: 1333 while inp:
1311 if len(inp) > 48: 1334 if len(inp) > 48:
1312 t = inp[:48] 1335 t = inp[:48]
1313 inp = inp[48:] 1336 inp = inp[48:]
1314 else: 1337 else:
1315 t = inp 1338 t = inp
1316 inp = '' 1339 inp = b''
1317 e = binascii.b2a_base64(t) 1340 e = binascii.b2a_base64(t)
1318 if e: 1341 if e:
1319 oup = oup + e[:-1] 1342 oup = oup + e[:-1]
1320 return oup 1343 return oup
1321 1344
1322 def decode(self, inp): 1345 def decode(self, inp):
1323 if not inp: 1346 if not inp:
1324 return '' 1347 return b''
1325 return binascii.a2b_base64(inp) 1348 return binascii.a2b_base64(inp)
1326 1349
1327 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(' ')
1328 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:])}
1329 1352
1330 def Internaldate2tuple(resp): 1353 def Internaldate2tuple(resp):
1331 """Parse an IMAP4 INTERNALDATE string. 1354 """Parse an IMAP4 INTERNALDATE string.
1332 1355
1333 Return corresponding local time. The return value is a 1356 Return corresponding local time. The return value is a
1334 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
1525 except: 1548 except:
1526 print('\nTests failed.') 1549 print('\nTests failed.')
1527 1550
1528 if not Debug: 1551 if not Debug:
1529 print(''' 1552 print('''
1530 If you would like to see debugging output, 1553 If you would like to see debugging output,
1531 try: %s -d5 1554 try: %s -d5
1532 ''' % sys.argv[0]) 1555 ''' % sys.argv[0])
1533 1556
1534 raise 1557 raise
LEFTRIGHT

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