@@ -94,14 +94,25 @@ class SecureTCPServer:
9494class SimpleIMAPHandler (socketserver .StreamRequestHandler ):
9595
9696 timeout = 1
97+ continuation = None
98+ capabilities = ''
9799
98100 def _send (self , message ):
99101 if verbose : print ("SENT: %r" % message .strip ())
100102 self .wfile .write (message )
101103
104+ def _send_line (self , message ):
105+ self ._send (message + b'\r \n ' )
106+
107+ def _send_textline (self , message ):
108+ self ._send_line (message .encode ('ASCII' ))
109+
110+ def _send_tagged (self , tag , code , message ):
111+ self ._send_textline (' ' .join ((tag , code , message )))
112+
102113 def handle (self ):
103114 # Send a welcome message.
104- self ._send ( b '* OK IMAP4rev1\r \n ' )
115+ self ._send_textline ( '* OK IMAP4rev1' )
105116 while 1 :
106117 # Gather up input until we receive a line terminator or we timeout.
107118 # Accumulate read(1) because it's simpler to handle the differences
@@ -121,19 +132,33 @@ def handle(self):
121132 break
122133
123134 if verbose : print ('GOT: %r' % line .strip ())
124- splitline = line .split ()
125- tag = splitline [0 ].decode ('ASCII' )
126- cmd = splitline [1 ].decode ('ASCII' )
135+ if self .continuation :
136+ try :
137+ self .continuation .send (line )
138+ except StopIteration :
139+ self .continuation = None
140+ continue
141+ splitline = line .decode ('ASCII' ).split ()
142+ tag = splitline [0 ]
143+ cmd = splitline [1 ]
127144 args = splitline [2 :]
128145
129146 if hasattr (self , 'cmd_' + cmd ):
130- getattr (self , 'cmd_' + cmd )(tag , args )
147+ continuation = getattr (self , 'cmd_' + cmd )(tag , args )
148+ if continuation :
149+ self .continuation = continuation
150+ next (continuation )
131151 else :
132- self ._send ( '{} BAD {} unknown \r \n ' . format ( tag , cmd ). encode ( 'ASCII' ) )
152+ self ._send_tagged ( tag , ' BAD' , cmd + ' unknown' )
133153
134154 def cmd_CAPABILITY (self , tag , args ):
135- self ._send (b'* CAPABILITY IMAP4rev1\r \n ' )
136- self ._send ('{} OK CAPABILITY completed\r \n ' .format (tag ).encode ('ASCII' ))
155+ caps = 'IMAP4rev1 ' + self .capabilities if self .capabilities else 'IMAP4rev1'
156+ self ._send_textline ('* CAPABILITY ' + caps )
157+ self ._send_tagged (tag , 'OK' , 'CAPABILITY completed' )
158+
159+ def cmd_LOGOUT (self , tag , args ):
160+ self ._send_textline ('* BYE IMAP4ref1 Server logging out' )
161+ self ._send_tagged (tag , 'OK' , 'LOGOUT completed' )
137162
138163
139164class BaseThreadedNetworkedTests (unittest .TestCase ):
@@ -183,6 +208,16 @@ def reaped_server(self, hdlr):
183208 finally :
184209 self .reap_server (server , thread )
185210
211+ @contextmanager
212+ def reaped_pair (self , hdlr ):
213+ server , thread = self .make_server ((support .HOST , 0 ), hdlr )
214+ client = self .imap_class (* server .server_address )
215+ try :
216+ yield server , client
217+ finally :
218+ client .logout ()
219+ self .reap_server (server , thread )
220+
186221 @reap_threads
187222 def test_connect (self ):
188223 with self .reaped_server (SimpleIMAPHandler ) as server :
@@ -208,12 +243,86 @@ class BadNewlineHandler(SimpleIMAPHandler):
208243
209244 def cmd_CAPABILITY (self , tag , args ):
210245 self ._send (b'* CAPABILITY IMAP4rev1 AUTH\n ' )
211- self ._send ( '{} OK CAPABILITY completed\r \n ' . format ( tag ). encode ( 'ASCII' ) )
246+ self ._send_tagged ( tag , 'OK' , ' CAPABILITY completed' )
212247
213248 with self .reaped_server (BadNewlineHandler ) as server :
214249 self .assertRaises (imaplib .IMAP4 .abort ,
215250 self .imap_class , * server .server_address )
216251
252+ @reap_threads
253+ def test_bad_auth_name (self ):
254+
255+ class MyServer (SimpleIMAPHandler ):
256+
257+ def cmd_AUTHENTICATE (self , tag , args ):
258+ self ._send_tagged (tag , 'NO' , 'unrecognized authentication '
259+ 'type {}' .format (args [0 ]))
260+
261+ with self .reaped_pair (MyServer ) as (server , client ):
262+ with self .assertRaises (imaplib .IMAP4 .error ):
263+ client .authenticate ('METHOD' , lambda : 1 )
264+
265+ @reap_threads
266+ def test_invalid_authentication (self ):
267+
268+ class MyServer (SimpleIMAPHandler ):
269+
270+ def cmd_AUTHENTICATE (self , tag , args ):
271+ self ._send_textline ('+' )
272+ self .response = yield
273+ self ._send_tagged (tag , 'NO' , '[AUTHENTICATIONFAILED] invalid' )
274+
275+ with self .reaped_pair (MyServer ) as (server , client ):
276+ with self .assertRaises (imaplib .IMAP4 .error ):
277+ code , data = client .authenticate ('MYAUTH' , lambda x : b'fake' )
278+
279+ @reap_threads
280+ def test_valid_authentication (self ):
281+
282+ class MyServer (SimpleIMAPHandler ):
283+
284+ def cmd_AUTHENTICATE (self , tag , args ):
285+ self ._send_textline ('+' )
286+ self .server .response = yield
287+ self ._send_tagged (tag , 'OK' , 'FAKEAUTH successful' )
288+
289+ with self .reaped_pair (MyServer ) as (server , client ):
290+ code , data = client .authenticate ('MYAUTH' , lambda x : b'fake' )
291+ self .assertEqual (code , 'OK' )
292+ self .assertEqual (server .response ,
293+ b'ZmFrZQ==\r \n ' ) #b64 encoded 'fake'
294+
295+ with self .reaped_pair (MyServer ) as (server , client ):
296+ code , data = client .authenticate ('MYAUTH' , lambda x : 'fake' )
297+ self .assertEqual (code , 'OK' )
298+ self .assertEqual (server .response ,
299+ b'ZmFrZQ==\r \n ' ) #b64 encoded 'fake'
300+
301+ @reap_threads
302+ def test_login_cram_md5 (self ):
303+
304+ class AuthHandler (SimpleIMAPHandler ):
305+
306+ capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
307+
308+ def cmd_AUTHENTICATE (self , tag , args ):
309+ self ._send_textline ('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
310+ 'VzdG9uLm1jaS5uZXQ=' )
311+ r = yield
312+ if r == b'dGltIGYxY2E2YmU0NjRiOWVmYTFjY2E2ZmZkNmNmMmQ5ZjMy\r \n ' :
313+ self ._send_tagged (tag , 'OK' , 'CRAM-MD5 successful' )
314+ else :
315+ self ._send_tagged (tag , 'NO' , 'No access' )
316+
317+ with self .reaped_pair (AuthHandler ) as (server , client ):
318+ self .assertTrue ('AUTH=CRAM-MD5' in client .capabilities )
319+ ret , data = client .login_cram_md5 ("tim" , "tanstaaftanstaaf" )
320+ self .assertEqual (ret , "OK" )
321+
322+ with self .reaped_pair (AuthHandler ) as (server , client ):
323+ self .assertTrue ('AUTH=CRAM-MD5' in client .capabilities )
324+ ret , data = client .login_cram_md5 ("tim" , b"tanstaaftanstaaf" )
325+ self .assertEqual (ret , "OK" )
217326
218327
219328class ThreadedNetworkedTests (BaseThreadedNetworkedTests ):
0 commit comments