Skip to content

Commit ccf900a

Browse files
serhiy-storchakaharjothkharaclaude
committed
gh-49680: Test exact APPEND and email message round-trip
Read the APPEND literal by its octet count and verify that a serialized email message with bare LF is sent verbatim when translate_line_endings is false. Document using email.policy.SMTP for that case. Co-authored-by: harjoth <harjoth.khara@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0d7e298 commit ccf900a

2 files changed

Lines changed: 34 additions & 13 deletions

File tree

Doc/library/imaplib.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ An :class:`IMAP4` instance has the following methods:
208208
line endings in *message* are translated to CRLF.
209209
Pass ``False`` to send the message literal exactly as given,
210210
which is required to preserve messages that contain bare CR or LF.
211+
In that case *message* must already use CRLF line endings as required
212+
by :rfc:`3501`; for example, serialize :mod:`email` messages using
213+
:class:`email.policy.SMTP`.
211214

212215
.. versionchanged:: next
213216
Added the *translate_line_endings* parameter.

Lib/test/test_imaplib.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
from test.support import socket_helper
33

44
from contextlib import contextmanager
5+
from email.message import EmailMessage
56
import imaplib
67
import os.path
78
import socketserver
89
import time
910
import calendar
1011
import threading
1112
import re
13+
import select
1214
import socket
1315

1416
from test.support import verbose, run_with_tz, run_with_locale, cpython_only
@@ -27,6 +29,18 @@
2729
CAFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "certdata", "pycacert.pem")
2830

2931

32+
def _read_literal(handler, marker):
33+
# Read one literal, a raw octet sequence, by its count from the marker
34+
# ('{N}', or '(~{N}' in UTF8 mode).
35+
size = int(re.search(r'\{(\d+)\}', marker).group(1))
36+
# The client must wait for the continuation, so nothing should be readable.
37+
if select.select([handler.connection], [], [], 0)[0]:
38+
raise AssertionError('client sent the literal before the '
39+
'continuation request')
40+
handler._send_textline('+')
41+
return handler.rfile.read(size)
42+
43+
3044
class TestImaplib(unittest.TestCase):
3145

3246
def test_Internaldate2tuple(self):
@@ -371,10 +385,8 @@ def cmd_AUTHENTICATE(self, tag, args):
371385
self.server.response = yield
372386
self._send_tagged(tag, 'OK', 'FAKEAUTH successful')
373387
def cmd_APPEND(self, tag, args):
374-
self._send_textline('+')
375388
self.server.response = args
376-
literal = yield
377-
self.server.response.append(literal)
389+
self.server.response.append(_read_literal(self, args[-1]))
378390
literal = yield
379391
self.server.response.append(literal)
380392
self._send_tagged(tag, 'OK', 'okay')
@@ -635,24 +647,32 @@ def test_login(self):
635647
self.assertEqual(client.state, 'AUTH')
636648

637649
def test_append_translate_line_endings(self):
638-
# By default line endings in the message are normalized to CRLF;
639-
# translate_line_endings=False sends the literal exactly (gh-49680).
650+
# By default line endings are normalized to CRLF; False sends the
651+
# literal exactly (gh-49680).
640652
class AppendHandler(SimpleIMAPHandler):
641653
def cmd_APPEND(self, tag, args):
642-
size = int(args[-1].strip('{}'))
643-
self._send_textline('+')
644-
self.server.response = self.rfile.read(size)
645-
self.rfile.readline() # trailing CRLF after the literal
654+
self.server.response = _read_literal(self, args[-1])
655+
yield # read the trailer line
646656
self._send_tagged(tag, 'OK', 'APPEND completed')
647-
message = b'a\rb\nc\r\nd'
648657
client, server = self._setup(AppendHandler)
649658
client.login('user', 'pass')
659+
message = b'a\rb\nc\r\nd'
650660
client.append('INBOX', None, None, message)
651661
self.assertEqual(server.response, b'a\r\nb\r\nc\r\nd')
652662
client.append('INBOX', None, None, message,
653663
translate_line_endings=False)
654664
self.assertEqual(server.response, message)
655665

666+
# An email message uses bare LF by default; False sends it verbatim.
667+
message = EmailMessage()
668+
message['Subject'] = 'line endings'
669+
message.set_content('body line\n')
670+
message = message.as_bytes()
671+
self.assertNotIn(b'\r\n', message)
672+
client.append('INBOX', None, None, message,
673+
translate_line_endings=False)
674+
self.assertEqual(server.response, message)
675+
656676
def test_logout(self):
657677
client, _ = self._setup(SimpleIMAPHandler)
658678
typ, data = client.login('user', 'pass')
@@ -944,10 +964,8 @@ def test_enable_UTF8_True_append(self):
944964

945965
class UTF8AppendServer(self.UTF8Server):
946966
def cmd_APPEND(self, tag, args):
947-
self._send_textline('+')
948967
self.server.response = args
949-
literal = yield
950-
self.server.response.append(literal)
968+
self.server.response.append(_read_literal(self, args[-1]))
951969
literal = yield
952970
self.server.response.append(literal)
953971
self._send_tagged(tag, 'OK', 'okay')

0 commit comments

Comments
 (0)