Skip to content

Commit fcaf355

Browse files
author
Forest
committed
imaplib: narrow the scope of IDLE socket timeouts
If an IDLE duration or burst() was in use, and an unsolicited response contained a literal string, and crossed a packet boundary, and the subsequent packet was delayed beyond the IDLE feature's time limit, the timeout would leave the incoming protocol stream in a bad state (with the tail of that response appearing where the start of a response is expected). This change moves the IDLE socket timeout to cover only the start of a response, so it can no longer cause that problem.
1 parent 53c7a19 commit fcaf355

1 file changed

Lines changed: 22 additions & 19 deletions

File tree

Lib/imaplib.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ class IMAP4:
186186
class error(Exception): pass # Logical errors - debug required
187187
class abort(error): pass # Service errors - close and retry
188188
class readonly(abort): pass # Mailbox status changed to READ-ONLY
189+
class _responsetimeout(TimeoutError): pass # No response during IDLE
189190

190191
def __init__(self, host='', port=IMAP4_PORT, timeout=None):
191192
self.debug = Debug
@@ -1154,14 +1155,28 @@ def _get_capabilities(self):
11541155
self.capabilities = tuple(dat.split())
11551156

11561157

1157-
def _get_response(self):
1158+
def _get_response(self, start_timeout=False):
11581159

11591160
# Read response and store.
11601161
#
11611162
# Returns None for continuation responses,
11621163
# otherwise first response line received.
1163-
1164-
resp = self._get_line()
1164+
#
1165+
# If start_timeout is given, temporarily uses it as a socket
1166+
# timeout while waiting for the start of a response, raising
1167+
# _responsetimeout if one doesn't arrive. (Used by Idler.)
1168+
1169+
if start_timeout is not False and self.sock:
1170+
assert start_timeout is None or start_timeout > 0
1171+
saved_timeout = self.sock.gettimeout()
1172+
self.sock.settimeout(start_timeout)
1173+
try:
1174+
resp = self._get_line()
1175+
except TimeoutError as err:
1176+
raise self._responsetimeout from err
1177+
finally:
1178+
if start_timeout is not False and self.sock:
1179+
self.sock.settimeout(saved_timeout)
11651180

11661181
# Command completion response?
11671182

@@ -1386,7 +1401,6 @@ def __init__(self, imap, duration=None):
13861401
self._deadline = None
13871402
self._imap = imap
13881403
self._tag = None
1389-
self._saved_timeout = None
13901404
self._saved_state = None
13911405

13921406
def __enter__(self):
@@ -1424,10 +1438,6 @@ def __enter__(self):
14241438
imap._idle_capture = False
14251439
raise
14261440

1427-
self._saved_timeout = imap.sock.gettimeout() if imap.sock else None
1428-
if self._saved_timeout is not None:
1429-
imap.sock.settimeout(None) # Socket timeout would break IDLE
1430-
14311441
if self._duration is not None:
14321442
self._deadline = time.monotonic() + self._duration
14331443

@@ -1443,10 +1453,6 @@ def __exit__(self, exc_type, exc_val, exc_tb):
14431453
imap._mesg('idle done')
14441454
imap.state = self._saved_state
14451455

1446-
if self._saved_timeout is not None:
1447-
imap.sock.settimeout(self._saved_timeout)
1448-
self._saved_timeout = None
1449-
14501456
# Stop intercepting untagged responses before sending DONE,
14511457
# since we can no longer deliver them via iteration.
14521458
imap._idle_capture = False
@@ -1514,19 +1520,16 @@ def _pop(self, timeout, default=('', None)):
15141520
imap._mesg(f'idle _pop({timeout}) reading')
15151521

15161522
if timeout is not None:
1517-
assert isinstance(imap.sock, socket.socket)
15181523
if timeout <= 0:
15191524
return default
1520-
imap.sock.settimeout(float(timeout))
1525+
timeout = float(timeout) # Required by socket.settimeout()
1526+
15211527
try:
1522-
imap._get_response() # Reads line, calls _append_untagged()
1523-
except TimeoutError:
1528+
imap._get_response(timeout) # Reads line, calls _append_untagged()
1529+
except IMAP4._responsetimeout:
15241530
if __debug__ and imap.debug >= 4:
15251531
imap._mesg(f'idle _pop({timeout}) done')
15261532
return default
1527-
finally:
1528-
if timeout is not None:
1529-
imap.sock.settimeout(None)
15301533

15311534
resp = imap._idle_responses.pop(0)
15321535

0 commit comments

Comments
 (0)