Skip to content

Commit 27ec185

Browse files
serhiy-storchakabitdancerclaude
committed
Address code review
Let _format_astring() fall through to the generic TypeError for a bool value instead of a special-case message (suggested by David Murray), and move the substitution note back into the body rather than the versionadded directive. Co-authored-by: R. David Murray <rdmurray@bitdance.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 99a671c commit 27ec185

3 files changed

Lines changed: 15 additions & 10 deletions

File tree

Doc/library/imaplib.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,14 +268,14 @@ The placeholders are:
268268

269269
``??`` stands for a literal ``?``.
270270

271+
Substitution is only performed when *params* is given;
272+
if no *params* are given, an argument containing a literal ``?`` is unchanged.
271273
The *params* keyword is accepted by :meth:`~IMAP4.search`,
272274
:meth:`~IMAP4.fetch`, :meth:`~IMAP4.sort`, :meth:`~IMAP4.thread` and
273275
:meth:`~IMAP4.uid`.
274276

275277
.. versionadded:: next
276278
The *params* keyword argument.
277-
Substitution is only performed when *params* is given,
278-
so an existing call that contains a literal ``?`` is unaffected.
279279

280280
An :class:`IMAP4` instance has the following methods:
281281

Lib/imaplib.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,7 @@ def _format_sequence_set(arg):
185185
def _format_astring(value):
186186
if isinstance(value, (list, tuple)):
187187
return '(' + ' '.join(map(_format_astring, value)) + ')'
188-
if isinstance(value, bool):
189-
raise TypeError('a boolean is not a valid IMAP4 string')
190-
if isinstance(value, int):
188+
if not isinstance(value, bool) and isinstance(value, int):
191189
return str(value)
192190
if isinstance(value, (bytes, bytearray)):
193191
value = str(value, 'ascii')
@@ -204,6 +202,8 @@ def _format_astring(value):
204202

205203
def _format_flags(value):
206204
if isinstance(value, (list, tuple)):
205+
# A nested sequence is not part of the API; it produces invalid
206+
# syntax that is rejected by the server.
207207
return '(' + ' '.join(map(_format_flags, value)) + ')'
208208
if isinstance(value, (bytes, bytearray)):
209209
value = str(value, 'ascii')

Lib/test/test_imaplib.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,21 +184,21 @@ def test_astring(self):
184184
self.assertEqual(m._astring(b'INBOX'), b'INBOX')
185185
# Names with protocol-sensitive characters are quoted.
186186
self.assertEqual(m._astring('New folder'), b'"New folder"')
187-
self.assertEqual(m._astring('a"b'), b'"a\\"b"')
188-
self.assertEqual(m._astring('a\\b'), b'"a\\\\b"')
187+
self.assertEqual(m._astring('a"b'), rb'"a\"b"')
188+
self.assertEqual(m._astring(r'a\b'), rb'"a\\b"')
189189
self.assertEqual(m._astring(''), b'""')
190190
self.assertEqual(m._astring('*'), b'"*"')
191191
# A well-formed quoted string is passed through unchanged.
192192
self.assertEqual(m._astring('"New folder"'), b'"New folder"')
193193
self.assertEqual(m._astring('""'), b'""')
194194
# Including a lenient (non-RFC) backslash escape, which the server
195195
# may accept.
196-
self.assertEqual(m._astring('"a\\b"'), b'"a\\b"')
196+
self.assertEqual(m._astring(r'"a\b"'), rb'"a\b"')
197197
# A string that only looks quoted but is not a single token is
198198
# quoted as data, closing the argument injection vector.
199199
self.assertEqual(m._astring('"a" SELECT evil "'),
200-
b'"\\"a\\" SELECT evil \\""')
201-
self.assertEqual(m._astring('"'), b'"\\""')
200+
rb'"\"a\" SELECT evil \""')
201+
self.assertEqual(m._astring('"'), rb'"\""')
202202
# Non-ASCII names are only allowed in a quoted string or a
203203
# literal, never in an atom (RFC 6855).
204204
m._encoding = 'utf-8'
@@ -344,6 +344,11 @@ def test_substitute_errors(self):
344344
self.assertRaises(ValueError, sub, '?', ['a\r\nb']) # CR/LF not inline
345345
self.assertRaises(TypeError, sub, '?', [True]) # bool is not a string
346346
self.assertRaises(TypeError, sub, '?', [1.5]) # float is not a string
347+
self.assertRaises(TypeError, sub, '?s', [['a']]) # not a message number
348+
self.assertRaises(TypeError, sub, '?s', [[1.5]]) # not a message number
349+
self.assertRaises(TypeError, sub, '?s', [[(1, 'a')]]) # not a message number
350+
self.assertRaises(ValueError, sub, '?s', [[(1,)]]) # not a range pair
351+
self.assertRaises(ValueError, sub, '?s', [[(1, 2, 3)]]) # not a range pair
347352

348353

349354
if ssl:

0 commit comments

Comments
 (0)