From f85360380bb46f076217d0890960ded624fb9581 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 2 Jul 2026 14:17:37 +0300 Subject: [PATCH] gh-49680: Test imaplib.IMAP4.append line-ending normalization (cherry picked from commit ddb6539c4976b18895bb5c5cd37d197c60fb459d) Co-authored-by: harjoth Co-Authored-By: Claude Opus 4.8 (1M context) --- Lib/test/test_imaplib.py | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index e797a625014603..cb62aef1ff52d4 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -9,6 +9,7 @@ import calendar import threading import re +import select import socket from test.support import verbose, run_with_tz, run_with_locale, cpython_only @@ -89,6 +90,18 @@ class Handler(SimpleIMAPHandler): return Handler +def _read_literal(handler, marker): + # Read one literal, a raw octet sequence, by its count from the marker + # ('{N}', or '(~{N}' in UTF8 mode). + size = int(re.search(r'\{(\d+)\}', marker).group(1)) + # The client must wait for the continuation, so nothing should be readable. + if select.select([handler.connection], [], [], 0)[0]: + raise AssertionError('client sent the literal before the ' + 'continuation request') + handler._send_textline('+') + return handler.rfile.read(size) + + class TestImaplib(unittest.TestCase): def test_Internaldate2tuple(self): @@ -474,10 +487,8 @@ def cmd_AUTHENTICATE(self, tag, args): self.server.response = yield self._send_tagged(tag, 'OK', 'FAKEAUTH successful') def cmd_APPEND(self, tag, args): - self._send_textline('+') self.server.response = args - literal = yield - self.server.response.append(literal) + self.server.response.append(_read_literal(self, args[-1])) literal = yield self.server.response.append(literal) self._send_tagged(tag, 'OK', 'okay') @@ -737,6 +748,19 @@ def test_login(self): self.assertEqual(data[0], b'LOGIN completed') self.assertEqual(client.state, 'AUTH') + def test_append_line_endings(self): + # append() normalizes bare CR and LF in the message to CRLF. + class AppendHandler(SimpleIMAPHandler): + def cmd_APPEND(self, tag, args): + self.server.response = _read_literal(self, args[-1]) + yield # read the trailer line + self._send_tagged(tag, 'OK', 'APPEND completed') + client, server = self._setup(AppendHandler) + client.login('user', 'pass') + message = b'a\rb\nc\r\nd' + client.append('INBOX', None, None, message) + self.assertEqual(server.response, b'a\r\nb\r\nc\r\nd') + def test_login_capabilities(self): # A server may advertise new capabilities after login (as an # untagged CAPABILITY response); imaplib must refresh its cached @@ -1673,10 +1697,8 @@ def test_enable_UTF8_True_append(self): class UTF8AppendServer(self.UTF8Server): def cmd_APPEND(self, tag, args): - self._send_textline('+') self.server.response = args - literal = yield - self.server.response.append(literal) + self.server.response.append(_read_literal(self, args[-1])) literal = yield self.server.response.append(literal) self._send_tagged(tag, 'OK', 'okay')