22from test .support import socket_helper
33
44from contextlib import contextmanager
5+ from email .message import EmailMessage
56import imaplib
67import os .path
78import socketserver
2728CAFILE = os .path .join (os .path .dirname (__file__ ) or os .curdir , "certdata" , "pycacert.pem" )
2829
2930
31+ def _read_append_literal (handler , args ):
32+ literal = args [- 1 ]
33+ trailer = b'\r \n '
34+ if literal .startswith ('(' ):
35+ literal = literal [1 :]
36+ trailer = b')\r \n '
37+ if literal .startswith ('~' ):
38+ literal = literal [1 :]
39+ match = re .fullmatch (r'\{(\d+)\}' , literal )
40+ if match is None :
41+ raise AssertionError (f"unexpected APPEND literal marker: { args [- 1 ]!r} " )
42+ literal_size = int (match .group (1 ))
43+
44+ handler ._send_textline ('+' )
45+ payload = handler .rfile .read (literal_size )
46+ received_trailer = handler .rfile .read (len (trailer ))
47+ return payload , received_trailer
48+
49+
3050class TestImaplib (unittest .TestCase ):
3151
3252 def test_Internaldate2tuple (self ):
@@ -371,12 +391,9 @@ def cmd_AUTHENTICATE(self, tag, args):
371391 self .server .response = yield
372392 self ._send_tagged (tag , 'OK' , 'FAKEAUTH successful' )
373393 def cmd_APPEND (self , tag , args ):
374- self ._send_textline ('+' )
375394 self .server .response = args
376- literal = yield
377- self .server .response .append (literal )
378- literal = yield
379- self .server .response .append (literal )
395+ payload , trailer = _read_append_literal (self , args )
396+ self .server .response .extend ([payload , trailer ])
380397 self ._send_tagged (tag , 'OK' , 'okay' )
381398 client , server = self ._setup (UTF8AppendServer )
382399 self .assertEqual (client ._encoding , 'ascii' )
@@ -387,14 +404,45 @@ def cmd_APPEND(self, tag, args):
387404 self .assertEqual (code , 'OK' )
388405 self .assertEqual (client ._encoding , 'utf-8' )
389406 msg_string = 'Subject: üñí©öðé'
390- typ , data = client .append (
391- None , None , None , (msg_string + '\n ' ).encode ('utf-8' ))
407+ msg = (msg_string + '\n ' ).encode ('utf-8' )
408+ self .assertEqual (len (msg ), 24 )
409+ typ , data = client .append (None , None , None , msg )
392410 self .assertEqual (typ , 'OK' )
393411 self .assertEqual (server .response ,
394412 ['INBOX' , 'UTF8' ,
395- '(~{25 }' , ( '%s \r \n ' % msg_string ). encode ( 'utf-8' ) ,
413+ '(~{24 }' , msg ,
396414 b')\r \n ' ])
397415
416+ def test_append_preserves_message_line_endings (self ):
417+ class AppendServer (SimpleIMAPHandler ):
418+ def cmd_APPEND (self , tag , args ):
419+ payload , trailer = _read_append_literal (self , args )
420+ self .server .responses .append (args + [payload , trailer ])
421+ self ._send_tagged (tag , 'OK' , 'okay' )
422+
423+ client , server = self ._setup (AppendServer )
424+ server .responses = []
425+ typ , _ = client .login ('user' , 'pass' )
426+ self .assertEqual (typ , 'OK' )
427+
428+ msg = b"one\n two\r three\r \n four"
429+ self .assertEqual (len (msg ), 19 )
430+ typ , _ = client .append (None , None , None , msg )
431+ self .assertEqual (typ , 'OK' )
432+ self .assertEqual (server .responses [- 1 ],
433+ ['INBOX' , '{19}' , msg , b'\r \n ' ])
434+
435+ email = EmailMessage ()
436+ email ['Subject' ] = 'line endings'
437+ email .set_content ('body line\n ' )
438+ msg = email .as_bytes ()
439+ self .assertIn (b'\n ' , msg )
440+ self .assertNotIn (b'\r \n ' , msg )
441+ typ , _ = client .append (None , None , None , msg )
442+ self .assertEqual (typ , 'OK' )
443+ self .assertEqual (server .responses [- 1 ],
444+ ['INBOX' , f'{{{ len (msg )} }}' , msg , b'\r \n ' ])
445+
398446 def test_search_disallows_charset_in_utf8_mode (self ):
399447 class UTF8Server (SimpleIMAPHandler ):
400448 capabilities = 'AUTH ENABLE UTF8=ACCEPT'
@@ -925,12 +973,9 @@ def test_enable_UTF8_True_append(self):
925973
926974 class UTF8AppendServer (self .UTF8Server ):
927975 def cmd_APPEND (self , tag , args ):
928- self ._send_textline ('+' )
929976 self .server .response = args
930- literal = yield
931- self .server .response .append (literal )
932- literal = yield
933- self .server .response .append (literal )
977+ payload , trailer = _read_append_literal (self , args )
978+ self .server .response .extend ([payload , trailer ])
934979 self ._send_tagged (tag , 'OK' , 'okay' )
935980
936981 with self .reaped_pair (UTF8AppendServer ) as (server , client ):
@@ -943,14 +988,47 @@ def cmd_APPEND(self, tag, args):
943988 self .assertEqual (code , 'OK' )
944989 self .assertEqual (client ._encoding , 'utf-8' )
945990 msg_string = 'Subject: üñí©öðé'
946- typ , data = client .append (
947- None , None , None , (msg_string + '\n ' ).encode ('utf-8' ))
991+ msg = (msg_string + '\n ' ).encode ('utf-8' )
992+ self .assertEqual (len (msg ), 24 )
993+ typ , data = client .append (None , None , None , msg )
948994 self .assertEqual (typ , 'OK' )
949995 self .assertEqual (server .response ,
950996 ['INBOX' , 'UTF8' ,
951- '(~{25 }' , ( '%s \r \n ' % msg_string ). encode ( 'utf-8' ) ,
997+ '(~{24 }' , msg ,
952998 b')\r \n ' ])
953999
1000+ @threading_helper .reap_threads
1001+ def test_append_preserves_message_line_endings (self ):
1002+
1003+ class AppendServer (SimpleIMAPHandler ):
1004+ def cmd_APPEND (self , tag , args ):
1005+ payload , trailer = _read_append_literal (self , args )
1006+ self .server .responses .append (args + [payload , trailer ])
1007+ self ._send_tagged (tag , 'OK' , 'okay' )
1008+
1009+ with self .reaped_pair (AppendServer ) as (server , client ):
1010+ server .responses = []
1011+ typ , _ = client .login ('user' , 'pass' )
1012+ self .assertEqual (typ , 'OK' )
1013+
1014+ msg = b"one\n two\r three\r \n four"
1015+ self .assertEqual (len (msg ), 19 )
1016+ typ , _ = client .append (None , None , None , msg )
1017+ self .assertEqual (typ , 'OK' )
1018+ self .assertEqual (server .responses [- 1 ],
1019+ ['INBOX' , '{19}' , msg , b'\r \n ' ])
1020+
1021+ email = EmailMessage ()
1022+ email ['Subject' ] = 'line endings'
1023+ email .set_content ('body line\n ' )
1024+ msg = email .as_bytes ()
1025+ self .assertIn (b'\n ' , msg )
1026+ self .assertNotIn (b'\r \n ' , msg )
1027+ typ , _ = client .append (None , None , None , msg )
1028+ self .assertEqual (typ , 'OK' )
1029+ self .assertEqual (server .responses [- 1 ],
1030+ ['INBOX' , f'{{{ len (msg )} }}' , msg , b'\r \n ' ])
1031+
9541032 # XXX also need a test that makes sure that the Literal and Untagged_status
9551033 # regexes uses unicode in UTF8 mode instead of the default ASCII.
9561034
0 commit comments