@@ -136,9 +136,11 @@ def _send_textline(self, message):
136136 def _send_tagged (self , tag , code , message ):
137137 self ._send_textline (' ' .join ((tag , code , message )))
138138
139+ welcome = '* OK IMAP4rev1'
140+
139141 def handle (self ):
140142 # Send a welcome message.
141- self ._send_textline ('* OK IMAP4rev1' )
143+ self ._send_textline (self . welcome )
142144 while 1 :
143145 # Gather up input until we receive a line terminator or we timeout.
144146 # Accumulate read(1) because it's simpler to handle the differences
@@ -634,6 +636,71 @@ def test_login(self):
634636 self .assertEqual (data [0 ], b'LOGIN completed' )
635637 self .assertEqual (client .state , 'AUTH' )
636638
639+ def test_login_capabilities (self ):
640+ # A server may advertise new capabilities after login (as an
641+ # untagged CAPABILITY response); imaplib must refresh its cached
642+ # capability list (gh-63121, gh-103451).
643+ class CapabilityLoginHandler (SimpleIMAPHandler ):
644+ def cmd_LOGIN (self , tag , args ):
645+ self .server .logged = args [0 ]
646+ self ._send_textline ('* CAPABILITY IMAP4rev1 ENABLE UTF8=ACCEPT' )
647+ self ._send_tagged (tag , 'OK' , 'LOGIN completed' )
648+ def cmd_ENABLE (self , tag , args ):
649+ self ._send_tagged (tag , 'OK' , 'ENABLE completed' )
650+
651+ client , _ = self ._setup (CapabilityLoginHandler )
652+ self .assertNotIn ('ENABLE' , client .capabilities )
653+ client .login ('user' , 'pass' )
654+ self .assertIn ('ENABLE' , client .capabilities )
655+ self .assertIn ('UTF8=ACCEPT' , client .capabilities )
656+ typ , _ = client .enable ('UTF8=ACCEPT' )
657+ self .assertEqual (typ , 'OK' )
658+
659+ def test_authenticate_capabilities (self ):
660+ # Capabilities are also refreshed after AUTHENTICATE, here from a
661+ # CAPABILITY response code in the tagged OK response.
662+ class CapabilityAuthHandler (SimpleIMAPHandler ):
663+ def cmd_AUTHENTICATE (self , tag , args ):
664+ self ._send_textline ('+' )
665+ self .server .response = yield
666+ self ._send_tagged (
667+ tag , 'OK' ,
668+ '[CAPABILITY IMAP4rev1 ENABLE] AUTHENTICATE completed' )
669+
670+ client , _ = self ._setup (CapabilityAuthHandler )
671+ self .assertNotIn ('ENABLE' , client .capabilities )
672+ client .authenticate ('MYAUTH' , lambda x : b'fake' )
673+ self .assertIn ('ENABLE' , client .capabilities )
674+
675+ def test_greeting_capabilities (self ):
676+ # Capabilities advertised in the greeting are used directly,
677+ # without sending a separate CAPABILITY command.
678+ class GreetingHandler (SimpleIMAPHandler ):
679+ welcome = '* OK [CAPABILITY IMAP4rev1 ENABLE] Server ready'
680+ def cmd_CAPABILITY (self , tag , args ):
681+ self .server .capability_queried = True
682+ super ().cmd_CAPABILITY (tag , args )
683+
684+ client , server = self ._setup (GreetingHandler )
685+ self .assertEqual (client .capabilities , ('IMAP4REV1' , 'ENABLE' ))
686+ self .assertFalse (getattr (server , 'capability_queried' , False ))
687+
688+ def test_login_requery_capabilities (self ):
689+ # If the server does not advertise capabilities after login,
690+ # imaplib re-queries them (as it does after STARTTLS), so a
691+ # capability that becomes available only after authentication is
692+ # still recognized (gh-63121).
693+ class RequeryHandler (SimpleIMAPHandler ):
694+ def cmd_CAPABILITY (self , tag , args ):
695+ caps = 'IMAP4rev1 ENABLE' if self .server .logged else 'IMAP4rev1'
696+ self ._send_textline ('* CAPABILITY ' + caps )
697+ self ._send_tagged (tag , 'OK' , 'CAPABILITY completed' )
698+
699+ client , _ = self ._setup (RequeryHandler )
700+ self .assertNotIn ('ENABLE' , client .capabilities )
701+ client .login ('user' , 'pass' )
702+ self .assertIn ('ENABLE' , client .capabilities )
703+
637704 def test_logout (self ):
638705 client , _ = self ._setup (SimpleIMAPHandler )
639706 typ , data = client .login ('user' , 'pass' )
0 commit comments