@@ -78,14 +78,25 @@ class SecureTCPServer:
7878class SimpleIMAPHandler (socketserver .StreamRequestHandler ):
7979
8080 timeout = 1
81+ continuation = None
82+ capabilities = ''
8183
8284 def _send (self , message ):
8385 if verbose : print ("SENT: %r" % message .strip ())
8486 self .wfile .write (message )
8587
88+ def _send_line (self , message ):
89+ self ._send (message + b'\r \n ' )
90+
91+ def _send_textline (self , message ):
92+ self ._send_line (message .encode ('ASCII' ))
93+
94+ def _send_tagged (self , tag , code , message ):
95+ self ._send_textline (' ' .join ((tag , code , message )))
96+
8697 def handle (self ):
8798 # Send a welcome message.
88- self ._send ( b '* OK IMAP4rev1\r \n ' )
99+ self ._send_textline ( '* OK IMAP4rev1' )
89100 while 1 :
90101 # Gather up input until we receive a line terminator or we timeout.
91102 # Accumulate read(1) because it's simpler to handle the differences
@@ -105,19 +116,33 @@ def handle(self):
105116 break
106117
107118 if verbose : print ('GOT: %r' % line .strip ())
108- splitline = line .split ()
109- tag = splitline [0 ].decode ('ASCII' )
110- cmd = splitline [1 ].decode ('ASCII' )
119+ if self .continuation :
120+ try :
121+ self .continuation .send (line )
122+ except StopIteration :
123+ self .continuation = None
124+ continue
125+ splitline = line .decode ('ASCII' ).split ()
126+ tag = splitline [0 ]
127+ cmd = splitline [1 ]
111128 args = splitline [2 :]
112129
113130 if hasattr (self , 'cmd_' + cmd ):
114- getattr (self , 'cmd_' + cmd )(tag , args )
131+ continuation = getattr (self , 'cmd_' + cmd )(tag , args )
132+ if continuation :
133+ self .continuation = continuation
134+ next (continuation )
115135 else :
116- self ._send ( '{} BAD {} unknown \r \n ' . format ( tag , cmd ). encode ( 'ASCII' ) )
136+ self ._send_tagged ( tag , ' BAD' , cmd + ' unknown' )
117137
118138 def cmd_CAPABILITY (self , tag , args ):
119- self ._send (b'* CAPABILITY IMAP4rev1\r \n ' )
120- self ._send ('{} OK CAPABILITY completed\r \n ' .format (tag ).encode ('ASCII' ))
139+ caps = 'IMAP4rev1 ' + self .capabilities if self .capabilities else 'IMAP4rev1'
140+ self ._send_textline ('* CAPABILITY ' + caps )
141+ self ._send_tagged (tag , 'OK' , 'CAPABILITY completed' )
142+
143+ def cmd_LOGOUT (self , tag , args ):
144+ self ._send_textline ('* BYE IMAP4ref1 Server logging out' )
145+ self ._send_tagged (tag , 'OK' , 'LOGOUT completed' )
121146
122147
123148class BaseThreadedNetworkedTests (unittest .TestCase ):
@@ -167,6 +192,16 @@ def reaped_server(self, hdlr):
167192 finally :
168193 self .reap_server (server , thread )
169194
195+ @contextmanager
196+ def reaped_pair (self , hdlr ):
197+ server , thread = self .make_server ((support .HOST , 0 ), hdlr )
198+ client = self .imap_class (* server .server_address )
199+ try :
200+ yield server , client
201+ finally :
202+ client .logout ()
203+ self .reap_server (server , thread )
204+
170205 @reap_threads
171206 def test_connect (self ):
172207 with self .reaped_server (SimpleIMAPHandler ) as server :
@@ -192,12 +227,86 @@ class BadNewlineHandler(SimpleIMAPHandler):
192227
193228 def cmd_CAPABILITY (self , tag , args ):
194229 self ._send (b'* CAPABILITY IMAP4rev1 AUTH\n ' )
195- self ._send ( '{} OK CAPABILITY completed\r \n ' . format ( tag ). encode ( 'ASCII' ) )
230+ self ._send_tagged ( tag , 'OK' , ' CAPABILITY completed' )
196231
197232 with self .reaped_server (BadNewlineHandler ) as server :
198233 self .assertRaises (imaplib .IMAP4 .abort ,
199234 self .imap_class , * server .server_address )
200235
236+ @reap_threads
237+ def test_bad_auth_name (self ):
238+
239+ class MyServer (SimpleIMAPHandler ):
240+
241+ def cmd_AUTHENTICATE (self , tag , args ):
242+ self ._send_tagged (tag , 'NO' , 'unrecognized authentication '
243+ 'type {}' .format (args [0 ]))
244+
245+ with self .reaped_pair (MyServer ) as (server , client ):
246+ with self .assertRaises (imaplib .IMAP4 .error ):
247+ client .authenticate ('METHOD' , lambda : 1 )
248+
249+ @reap_threads
250+ def test_invalid_authentication (self ):
251+
252+ class MyServer (SimpleIMAPHandler ):
253+
254+ def cmd_AUTHENTICATE (self , tag , args ):
255+ self ._send_textline ('+' )
256+ self .response = yield
257+ self ._send_tagged (tag , 'NO' , '[AUTHENTICATIONFAILED] invalid' )
258+
259+ with self .reaped_pair (MyServer ) as (server , client ):
260+ with self .assertRaises (imaplib .IMAP4 .error ):
261+ code , data = client .authenticate ('MYAUTH' , lambda x : b'fake' )
262+
263+ @reap_threads
264+ def test_valid_authentication (self ):
265+
266+ class MyServer (SimpleIMAPHandler ):
267+
268+ def cmd_AUTHENTICATE (self , tag , args ):
269+ self ._send_textline ('+' )
270+ self .server .response = yield
271+ self ._send_tagged (tag , 'OK' , 'FAKEAUTH successful' )
272+
273+ with self .reaped_pair (MyServer ) as (server , client ):
274+ code , data = client .authenticate ('MYAUTH' , lambda x : b'fake' )
275+ self .assertEqual (code , 'OK' )
276+ self .assertEqual (server .response ,
277+ b'ZmFrZQ==\r \n ' ) #b64 encoded 'fake'
278+
279+ with self .reaped_pair (MyServer ) as (server , client ):
280+ code , data = client .authenticate ('MYAUTH' , lambda x : 'fake' )
281+ self .assertEqual (code , 'OK' )
282+ self .assertEqual (server .response ,
283+ b'ZmFrZQ==\r \n ' ) #b64 encoded 'fake'
284+
285+ @reap_threads
286+ def test_login_cram_md5 (self ):
287+
288+ class AuthHandler (SimpleIMAPHandler ):
289+
290+ capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
291+
292+ def cmd_AUTHENTICATE (self , tag , args ):
293+ self ._send_textline ('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
294+ 'VzdG9uLm1jaS5uZXQ=' )
295+ r = yield
296+ if r == b'dGltIGYxY2E2YmU0NjRiOWVmYTFjY2E2ZmZkNmNmMmQ5ZjMy\r \n ' :
297+ self ._send_tagged (tag , 'OK' , 'CRAM-MD5 successful' )
298+ else :
299+ self ._send_tagged (tag , 'NO' , 'No access' )
300+
301+ with self .reaped_pair (AuthHandler ) as (server , client ):
302+ self .assertTrue ('AUTH=CRAM-MD5' in client .capabilities )
303+ ret , data = client .login_cram_md5 ("tim" , "tanstaaftanstaaf" )
304+ self .assertEqual (ret , "OK" )
305+
306+ with self .reaped_pair (AuthHandler ) as (server , client ):
307+ self .assertTrue ('AUTH=CRAM-MD5' in client .capabilities )
308+ ret , data = client .login_cram_md5 ("tim" , b"tanstaaftanstaaf" )
309+ self .assertEqual (ret , "OK" )
201310
202311
203312class ThreadedNetworkedTests (BaseThreadedNetworkedTests ):
0 commit comments