An :class:`IMAP4` instance has the following methods:
-.. method:: IMAP4.append(mailbox, flags, date_time, message)
+.. method:: IMAP4.append(mailbox, flags, date_time, message, *, translate_line_endings=True)
Append *message* to named mailbox.
If *flags* is not already enclosed in parentheses, parentheses are
added automatically.
+ If *translate_line_endings* is true (the default),
+ line endings in *message* are translated to CRLF.
+ Pass ``False`` to send the message literal exactly as given,
+ which is required to preserve messages that contain bare CR or LF.
+ In that case *message* must already use CRLF line endings as required
+ by :rfc:`3501`; for example, serialize :mod:`email` messages using
+ :class:`email.policy.SMTP`.
+
+ .. versionchanged:: next
+ Added the *translate_line_endings* parameter.
+
.. method:: IMAP4.authenticate(mechanism, authobject)
# IMAP4 commands
- def append(self, mailbox, flags, date_time, message):
+ def append(self, mailbox, flags, date_time, message, *,
+ translate_line_endings=True):
"""Append message to named mailbox.
(typ, [data]) = <instance>.append(mailbox, flags, date_time, message)
All args except 'message' can be None.
+
+ If 'translate_line_endings' is true (the default), line endings in
+ 'message' are translated to CRLF. Pass false to send the message
+ literal exactly as given.
"""
name = 'APPEND'
if not mailbox:
date_time = Time2Internaldate(date_time)
else:
date_time = None
- literal = MapCRLF.sub(CRLF, message)
- self.literal = literal
+ if translate_line_endings:
+ message = MapCRLF.sub(CRLF, message)
+ self.literal = message
return self._simple_command(name, mailbox, flags, date_time)
from test.support import socket_helper
from contextlib import contextmanager
+from email.message import EmailMessage
import imaplib
import os.path
import socketserver
import calendar
import threading
import re
+import select
import socket
from test.support import verbose, run_with_tz, run_with_locale, cpython_only
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):
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')
self.assertEqual(data[0], b'LOGIN completed')
self.assertEqual(client.state, 'AUTH')
+ def test_append_translate_line_endings(self):
+ # By default line endings are normalized to CRLF; False sends the
+ # literal exactly (gh-49680).
+ 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')
+ client.append('INBOX', None, None, message,
+ translate_line_endings=False)
+ self.assertEqual(server.response, message)
+
+ # An email message uses bare LF by default; False sends it verbatim.
+ message = EmailMessage()
+ message['Subject'] = 'line endings'
+ message.set_content('body line\n')
+ message = message.as_bytes()
+ self.assertNotIn(b'\r\n', message)
+ client.append('INBOX', None, None, message,
+ translate_line_endings=False)
+ self.assertEqual(server.response, message)
+
def test_login_capabilities(self):
# A server may advertise new capabilities after login (as an
# untagged CAPABILITY response); imaplib must refresh its cached
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')