blob: 69ee63b18c373881fa17c22d11eae1916eea7dc8 [file] [log] [blame]
R. David Murraye8dc2582009-12-10 02:08:06 +00001from test import support
Serhiy Storchaka16994912020-04-25 10:06:29 +03002from test.support import socket_helper
R. David Murraye8dc2582009-12-10 02:08:06 +00003
4from contextlib import contextmanager
Martin v. Löwisea752fb2002-01-05 11:31:49 +00005import imaplib
R. David Murraye8dc2582009-12-10 02:08:06 +00006import os.path
R. David Murraye8dc2582009-12-10 02:08:06 +00007import socketserver
Tim Peters108b7912002-07-31 16:42:33 +00008import time
Alexander Belopolsky7dabf162011-01-29 19:49:40 +00009import calendar
Antoine Pitroua6a4dc82017-09-07 18:56:24 +020010import threading
Matěj Cepl3dc67d02019-02-12 19:30:19 +010011import socket
Martin v. Löwisea752fb2002-01-05 11:31:49 +000012
Antoine Pitroucac9e712014-07-31 18:35:45 -040013from test.support import (reap_threads, verbose, transient_internet,
Christian Heimesc64a1a62019-09-25 16:30:20 +020014 run_with_tz, run_with_locale, cpython_only,
15 requires_hashdigest)
Christian Heimesf6cd9672008-03-26 13:45:42 +000016import unittest
R David Murrayb079c072016-12-24 21:32:26 -050017from unittest import mock
Alexander Belopolsky8141cc72012-06-22 21:03:39 -040018from datetime import datetime, timezone, timedelta
R. David Murraye8dc2582009-12-10 02:08:06 +000019try:
20 import ssl
21except ImportError:
22 ssl = None
23
Antoine Pitroucac9e712014-07-31 18:35:45 -040024CERTFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "keycert3.pem")
25CAFILE = os.path.join(os.path.dirname(__file__) or os.curdir, "pycacert.pem")
R. David Murraye8dc2582009-12-10 02:08:06 +000026
Martin v. Löwisea752fb2002-01-05 11:31:49 +000027
Christian Heimesf6cd9672008-03-26 13:45:42 +000028class TestImaplib(unittest.TestCase):
R. David Murraye8dc2582009-12-10 02:08:06 +000029
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +000030 def test_Internaldate2tuple(self):
Alexander Belopolsky7dabf162011-01-29 19:49:40 +000031 t0 = calendar.timegm((2000, 1, 1, 0, 0, 0, -1, -1, -1))
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +000032 tt = imaplib.Internaldate2tuple(
Alexander Belopolsky7dabf162011-01-29 19:49:40 +000033 b'25 (INTERNALDATE "01-Jan-2000 00:00:00 +0000")')
34 self.assertEqual(time.mktime(tt), t0)
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +000035 tt = imaplib.Internaldate2tuple(
Alexander Belopolsky7dabf162011-01-29 19:49:40 +000036 b'25 (INTERNALDATE "01-Jan-2000 11:30:00 +1130")')
37 self.assertEqual(time.mktime(tt), t0)
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +000038 tt = imaplib.Internaldate2tuple(
Alexander Belopolsky7dabf162011-01-29 19:49:40 +000039 b'25 (INTERNALDATE "31-Dec-1999 12:30:00 -1130")')
40 self.assertEqual(time.mktime(tt), t0)
Alexander Belopolsky19e0a9e2011-01-29 17:19:08 +000041
Alexander Belopolsky2420d832012-04-29 15:56:49 -040042 @run_with_tz('MST+07MDT,M4.1.0,M10.5.0')
43 def test_Internaldate2tuple_issue10941(self):
44 self.assertNotEqual(imaplib.Internaldate2tuple(
45 b'25 (INTERNALDATE "02-Apr-2000 02:30:00 +0000")'),
Antoine Pitroucac9e712014-07-31 18:35:45 -040046 imaplib.Internaldate2tuple(
47 b'25 (INTERNALDATE "02-Apr-2000 03:30:00 +0000")'))
Alexander Belopolsky8141cc72012-06-22 21:03:39 -040048
49 def timevalues(self):
50 return [2000000000, 2000000000.0, time.localtime(2000000000),
51 (2033, 5, 18, 5, 33, 20, -1, -1, -1),
52 (2033, 5, 18, 5, 33, 20, -1, -1, 1),
Alexander Belopolsky64892132012-06-22 21:10:50 -040053 datetime.fromtimestamp(2000000000,
Antoine Pitroucac9e712014-07-31 18:35:45 -040054 timezone(timedelta(0, 2 * 60 * 60))),
Alexander Belopolsky8141cc72012-06-22 21:03:39 -040055 '"18-May-2033 05:33:20 +0200"']
56
57 @run_with_locale('LC_ALL', 'de_DE', 'fr_FR')
Martin Panter6e0889f2015-11-16 07:21:38 +000058 # DST rules included to work around quirk where the Gnu C library may not
59 # otherwise restore the previous time zone
60 @run_with_tz('STD-1DST,M3.2.0,M11.1.0')
Alexander Belopolsky8141cc72012-06-22 21:03:39 -040061 def test_Time2Internaldate(self):
62 expected = '"18-May-2033 05:33:20 +0200"'
63
64 for t in self.timevalues():
65 internal = imaplib.Time2Internaldate(t)
66 self.assertEqual(internal, expected)
67
68 def test_that_Time2Internaldate_returns_a_result(self):
69 # Without tzset, we can check only that it successfully
70 # produces a result, not the correctness of the result itself,
71 # since the result depends on the timezone the machine is in.
72 for t in self.timevalues():
Christian Heimesf6cd9672008-03-26 13:45:42 +000073 imaplib.Time2Internaldate(t)
74
Berker Peksage4dcbbd2018-08-07 05:12:18 +030075 def test_imap4_host_default_value(self):
Matěj Cepl3dc67d02019-02-12 19:30:19 +010076 # Check whether the IMAP4_PORT is truly unavailable.
77 with socket.socket() as s:
78 try:
79 s.connect(('', imaplib.IMAP4_PORT))
80 self.skipTest(
81 "Cannot run the test with local IMAP server running.")
82 except socket.error:
83 pass
84
Victor Stinner3c7931e2019-04-15 12:34:53 +020085 # This is the exception that should be raised.
Serhiy Storchaka16994912020-04-25 10:06:29 +030086 expected_errnos = socket_helper.get_socket_conn_refused_errs()
Berker Peksage4dcbbd2018-08-07 05:12:18 +030087 with self.assertRaises(OSError) as cm:
88 imaplib.IMAP4()
89 self.assertIn(cm.exception.errno, expected_errnos)
90
Christian Heimesf6cd9672008-03-26 13:45:42 +000091
R. David Murraye8dc2582009-12-10 02:08:06 +000092if ssl:
R. David Murraye8dc2582009-12-10 02:08:06 +000093 class SecureTCPServer(socketserver.TCPServer):
94
95 def get_request(self):
96 newsocket, fromaddr = self.socket.accept()
Christian Heimesd0486372016-09-10 23:23:33 +020097 context = ssl.SSLContext()
98 context.load_cert_chain(CERTFILE)
99 connstream = context.wrap_socket(newsocket, server_side=True)
R. David Murraye8dc2582009-12-10 02:08:06 +0000100 return connstream, fromaddr
101
102 IMAP4_SSL = imaplib.IMAP4_SSL
103
104else:
105
106 class SecureTCPServer:
107 pass
108
109 IMAP4_SSL = None
110
111
112class SimpleIMAPHandler(socketserver.StreamRequestHandler):
Victor Stinner07871b22019-12-10 20:32:59 +0100113 timeout = support.LOOPBACK_TIMEOUT
R David Murray774a39f2013-02-19 12:17:31 -0500114 continuation = None
115 capabilities = ''
R. David Murraye8dc2582009-12-10 02:08:06 +0000116
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300117 def setup(self):
118 super().setup()
Dong-hee Nac5c42812020-04-27 23:52:55 +0900119 self.server.is_selected = False
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300120 self.server.logged = None
121
R. David Murraye8dc2582009-12-10 02:08:06 +0000122 def _send(self, message):
Antoine Pitroucac9e712014-07-31 18:35:45 -0400123 if verbose:
124 print("SENT: %r" % message.strip())
R. David Murraye8dc2582009-12-10 02:08:06 +0000125 self.wfile.write(message)
126
R David Murray774a39f2013-02-19 12:17:31 -0500127 def _send_line(self, message):
128 self._send(message + b'\r\n')
129
130 def _send_textline(self, message):
131 self._send_line(message.encode('ASCII'))
132
133 def _send_tagged(self, tag, code, message):
134 self._send_textline(' '.join((tag, code, message)))
135
R. David Murraye8dc2582009-12-10 02:08:06 +0000136 def handle(self):
137 # Send a welcome message.
R David Murray774a39f2013-02-19 12:17:31 -0500138 self._send_textline('* OK IMAP4rev1')
R. David Murraye8dc2582009-12-10 02:08:06 +0000139 while 1:
140 # Gather up input until we receive a line terminator or we timeout.
141 # Accumulate read(1) because it's simpler to handle the differences
142 # between naked sockets and SSL sockets.
143 line = b''
144 while 1:
145 try:
146 part = self.rfile.read(1)
147 if part == b'':
148 # Naked sockets return empty strings..
149 return
150 line += part
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200151 except OSError:
Andrew Svetlov737fb892012-12-18 21:14:22 +0200152 # ..but SSLSockets raise exceptions.
R. David Murraye8dc2582009-12-10 02:08:06 +0000153 return
154 if line.endswith(b'\r\n'):
155 break
156
Antoine Pitroucac9e712014-07-31 18:35:45 -0400157 if verbose:
158 print('GOT: %r' % line.strip())
R David Murray774a39f2013-02-19 12:17:31 -0500159 if self.continuation:
160 try:
161 self.continuation.send(line)
162 except StopIteration:
163 self.continuation = None
164 continue
165 splitline = line.decode('ASCII').split()
166 tag = splitline[0]
167 cmd = splitline[1]
R. David Murraye8dc2582009-12-10 02:08:06 +0000168 args = splitline[2:]
169
Antoine Pitroucac9e712014-07-31 18:35:45 -0400170 if hasattr(self, 'cmd_' + cmd):
171 continuation = getattr(self, 'cmd_' + cmd)(tag, args)
R David Murray774a39f2013-02-19 12:17:31 -0500172 if continuation:
173 self.continuation = continuation
174 next(continuation)
R. David Murraye8dc2582009-12-10 02:08:06 +0000175 else:
R David Murray774a39f2013-02-19 12:17:31 -0500176 self._send_tagged(tag, 'BAD', cmd + ' unknown')
R. David Murraye8dc2582009-12-10 02:08:06 +0000177
178 def cmd_CAPABILITY(self, tag, args):
Antoine Pitroucac9e712014-07-31 18:35:45 -0400179 caps = ('IMAP4rev1 ' + self.capabilities
180 if self.capabilities
181 else 'IMAP4rev1')
R David Murray774a39f2013-02-19 12:17:31 -0500182 self._send_textline('* CAPABILITY ' + caps)
183 self._send_tagged(tag, 'OK', 'CAPABILITY completed')
184
185 def cmd_LOGOUT(self, tag, args):
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300186 self.server.logged = None
R David Murray774a39f2013-02-19 12:17:31 -0500187 self._send_textline('* BYE IMAP4ref1 Server logging out')
188 self._send_tagged(tag, 'OK', 'LOGOUT completed')
R. David Murraye8dc2582009-12-10 02:08:06 +0000189
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300190 def cmd_LOGIN(self, tag, args):
191 self.server.logged = args[0]
192 self._send_tagged(tag, 'OK', 'LOGIN completed')
193
Dong-hee Nac5c42812020-04-27 23:52:55 +0900194 def cmd_SELECT(self, tag, args):
195 self.server.is_selected = True
196 self._send_line(b'* 2 EXISTS')
197 self._send_tagged(tag, 'OK', '[READ-WRITE] SELECT completed.')
198
199 def cmd_UNSELECT(self, tag, args):
200 if self.server.is_selected:
201 self.server.is_selected = False
202 self._send_tagged(tag, 'OK', 'Returned to authenticated state. (Success)')
203 else:
204 self._send_tagged(tag, 'BAD', 'No mailbox selected')
205
R. David Murraye8dc2582009-12-10 02:08:06 +0000206
R David Murrayb079c072016-12-24 21:32:26 -0500207class NewIMAPTestsMixin():
208 client = None
209
210 def _setup(self, imap_handler, connect=True):
211 """
212 Sets up imap_handler for tests. imap_handler should inherit from either:
213 - SimpleIMAPHandler - for testing IMAP commands,
214 - socketserver.StreamRequestHandler - if raw access to stream is needed.
215 Returns (client, server).
216 """
217 class TestTCPServer(self.server_class):
218 def handle_error(self, request, client_address):
219 """
220 End request and raise the error if one occurs.
221 """
222 self.close_request(request)
223 self.server_close()
224 raise
225
226 self.addCleanup(self._cleanup)
Serhiy Storchaka16994912020-04-25 10:06:29 +0300227 self.server = self.server_class((socket_helper.HOST, 0), imap_handler)
R David Murrayb079c072016-12-24 21:32:26 -0500228 self.thread = threading.Thread(
229 name=self._testMethodName+'-server',
230 target=self.server.serve_forever,
231 # Short poll interval to make the test finish quickly.
232 # Time between requests is short enough that we won't wake
233 # up spuriously too many times.
234 kwargs={'poll_interval': 0.01})
235 self.thread.daemon = True # In case this function raises.
236 self.thread.start()
237
238 if connect:
239 self.client = self.imap_class(*self.server.server_address)
240
241 return self.client, self.server
242
243 def _cleanup(self):
244 """
245 Cleans up the test server. This method should not be called manually,
246 it is added to the cleanup queue in the _setup method already.
247 """
248 # if logout was called already we'd raise an exception trying to
249 # shutdown the client once again
250 if self.client is not None and self.client.state != 'LOGOUT':
251 self.client.shutdown()
252 # cleanup the server
253 self.server.shutdown()
254 self.server.server_close()
Victor Stinnerbbc8b792019-12-10 20:41:23 +0100255 support.join_thread(self.thread)
Victor Stinnerb9b69002017-09-14 14:40:56 -0700256 # Explicitly clear the attribute to prevent dangling thread
257 self.thread = None
R David Murrayb079c072016-12-24 21:32:26 -0500258
259 def test_EOF_without_complete_welcome_message(self):
260 # http://bugs.python.org/issue5949
261 class EOFHandler(socketserver.StreamRequestHandler):
262 def handle(self):
263 self.wfile.write(b'* OK')
264 _, server = self._setup(EOFHandler, connect=False)
265 self.assertRaises(imaplib.IMAP4.abort, self.imap_class,
266 *server.server_address)
267
268 def test_line_termination(self):
269 class BadNewlineHandler(SimpleIMAPHandler):
270 def cmd_CAPABILITY(self, tag, args):
271 self._send(b'* CAPABILITY IMAP4rev1 AUTH\n')
272 self._send_tagged(tag, 'OK', 'CAPABILITY completed')
273 _, server = self._setup(BadNewlineHandler, connect=False)
274 self.assertRaises(imaplib.IMAP4.abort, self.imap_class,
275 *server.server_address)
276
277 def test_enable_raises_error_if_not_AUTH(self):
278 class EnableHandler(SimpleIMAPHandler):
279 capabilities = 'AUTH ENABLE UTF8=ACCEPT'
280 client, _ = self._setup(EnableHandler)
281 self.assertFalse(client.utf8_enabled)
282 with self.assertRaisesRegex(imaplib.IMAP4.error, 'ENABLE.*NONAUTH'):
283 client.enable('foo')
284 self.assertFalse(client.utf8_enabled)
285
286 def test_enable_raises_error_if_no_capability(self):
287 client, _ = self._setup(SimpleIMAPHandler)
288 with self.assertRaisesRegex(imaplib.IMAP4.error,
289 'does not support ENABLE'):
290 client.enable('foo')
291
292 def test_enable_UTF8_raises_error_if_not_supported(self):
293 client, _ = self._setup(SimpleIMAPHandler)
294 typ, data = client.login('user', 'pass')
295 self.assertEqual(typ, 'OK')
296 with self.assertRaisesRegex(imaplib.IMAP4.error,
297 'does not support ENABLE'):
298 client.enable('UTF8=ACCEPT')
299
300 def test_enable_UTF8_True_append(self):
301 class UTF8AppendServer(SimpleIMAPHandler):
302 capabilities = 'ENABLE UTF8=ACCEPT'
303 def cmd_ENABLE(self, tag, args):
304 self._send_tagged(tag, 'OK', 'ENABLE successful')
305 def cmd_AUTHENTICATE(self, tag, args):
306 self._send_textline('+')
307 self.server.response = yield
308 self._send_tagged(tag, 'OK', 'FAKEAUTH successful')
309 def cmd_APPEND(self, tag, args):
310 self._send_textline('+')
311 self.server.response = yield
312 self._send_tagged(tag, 'OK', 'okay')
313 client, server = self._setup(UTF8AppendServer)
314 self.assertEqual(client._encoding, 'ascii')
315 code, _ = client.authenticate('MYAUTH', lambda x: b'fake')
316 self.assertEqual(code, 'OK')
317 self.assertEqual(server.response, b'ZmFrZQ==\r\n') # b64 encoded 'fake'
318 code, _ = client.enable('UTF8=ACCEPT')
319 self.assertEqual(code, 'OK')
320 self.assertEqual(client._encoding, 'utf-8')
321 msg_string = 'Subject: üñí©öðé'
322 typ, data = client.append(None, None, None, msg_string.encode('utf-8'))
323 self.assertEqual(typ, 'OK')
324 self.assertEqual(server.response,
325 ('UTF8 (%s)\r\n' % msg_string).encode('utf-8'))
326
327 def test_search_disallows_charset_in_utf8_mode(self):
328 class UTF8Server(SimpleIMAPHandler):
329 capabilities = 'AUTH ENABLE UTF8=ACCEPT'
330 def cmd_ENABLE(self, tag, args):
331 self._send_tagged(tag, 'OK', 'ENABLE successful')
332 def cmd_AUTHENTICATE(self, tag, args):
333 self._send_textline('+')
334 self.server.response = yield
335 self._send_tagged(tag, 'OK', 'FAKEAUTH successful')
336 client, _ = self._setup(UTF8Server)
337 typ, _ = client.authenticate('MYAUTH', lambda x: b'fake')
338 self.assertEqual(typ, 'OK')
339 typ, _ = client.enable('UTF8=ACCEPT')
340 self.assertEqual(typ, 'OK')
341 self.assertTrue(client.utf8_enabled)
342 with self.assertRaisesRegex(imaplib.IMAP4.error, 'charset.*UTF8'):
343 client.search('foo', 'bar')
344
345 def test_bad_auth_name(self):
346 class MyServer(SimpleIMAPHandler):
347 def cmd_AUTHENTICATE(self, tag, args):
348 self._send_tagged(tag, 'NO',
349 'unrecognized authentication type {}'.format(args[0]))
350 client, _ = self._setup(MyServer)
351 with self.assertRaisesRegex(imaplib.IMAP4.error,
352 'unrecognized authentication type METHOD'):
353 client.authenticate('METHOD', lambda: 1)
354
355 def test_invalid_authentication(self):
356 class MyServer(SimpleIMAPHandler):
357 def cmd_AUTHENTICATE(self, tag, args):
358 self._send_textline('+')
359 self.response = yield
360 self._send_tagged(tag, 'NO', '[AUTHENTICATIONFAILED] invalid')
361 client, _ = self._setup(MyServer)
362 with self.assertRaisesRegex(imaplib.IMAP4.error,
363 r'\[AUTHENTICATIONFAILED\] invalid'):
364 client.authenticate('MYAUTH', lambda x: b'fake')
365
366 def test_valid_authentication_bytes(self):
367 class MyServer(SimpleIMAPHandler):
368 def cmd_AUTHENTICATE(self, tag, args):
369 self._send_textline('+')
370 self.server.response = yield
371 self._send_tagged(tag, 'OK', 'FAKEAUTH successful')
372 client, server = self._setup(MyServer)
373 code, _ = client.authenticate('MYAUTH', lambda x: b'fake')
374 self.assertEqual(code, 'OK')
375 self.assertEqual(server.response, b'ZmFrZQ==\r\n') # b64 encoded 'fake'
376
377 def test_valid_authentication_plain_text(self):
378 class MyServer(SimpleIMAPHandler):
379 def cmd_AUTHENTICATE(self, tag, args):
380 self._send_textline('+')
381 self.server.response = yield
382 self._send_tagged(tag, 'OK', 'FAKEAUTH successful')
383 client, server = self._setup(MyServer)
384 code, _ = client.authenticate('MYAUTH', lambda x: 'fake')
385 self.assertEqual(code, 'OK')
386 self.assertEqual(server.response, b'ZmFrZQ==\r\n') # b64 encoded 'fake'
387
Christian Heimesc64a1a62019-09-25 16:30:20 +0200388 @requires_hashdigest('md5')
R David Murrayb079c072016-12-24 21:32:26 -0500389 def test_login_cram_md5_bytes(self):
390 class AuthHandler(SimpleIMAPHandler):
391 capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
392 def cmd_AUTHENTICATE(self, tag, args):
393 self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
394 'VzdG9uLm1jaS5uZXQ=')
395 r = yield
396 if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
397 b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
398 self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
399 else:
400 self._send_tagged(tag, 'NO', 'No access')
401 client, _ = self._setup(AuthHandler)
402 self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
403 ret, _ = client.login_cram_md5("tim", b"tanstaaftanstaaf")
404 self.assertEqual(ret, "OK")
405
Christian Heimesc64a1a62019-09-25 16:30:20 +0200406 @requires_hashdigest('md5')
R David Murrayb079c072016-12-24 21:32:26 -0500407 def test_login_cram_md5_plain_text(self):
408 class AuthHandler(SimpleIMAPHandler):
409 capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
410 def cmd_AUTHENTICATE(self, tag, args):
411 self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
412 'VzdG9uLm1jaS5uZXQ=')
413 r = yield
414 if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
415 b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
416 self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
417 else:
418 self._send_tagged(tag, 'NO', 'No access')
419 client, _ = self._setup(AuthHandler)
420 self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
421 ret, _ = client.login_cram_md5("tim", "tanstaaftanstaaf")
422 self.assertEqual(ret, "OK")
423
424 def test_aborted_authentication(self):
425 class MyServer(SimpleIMAPHandler):
426 def cmd_AUTHENTICATE(self, tag, args):
427 self._send_textline('+')
428 self.response = yield
429 if self.response == b'*\r\n':
430 self._send_tagged(
431 tag,
432 'NO',
433 '[AUTHENTICATIONFAILED] aborted')
434 else:
435 self._send_tagged(tag, 'OK', 'MYAUTH successful')
436 client, _ = self._setup(MyServer)
437 with self.assertRaisesRegex(imaplib.IMAP4.error,
438 r'\[AUTHENTICATIONFAILED\] aborted'):
439 client.authenticate('MYAUTH', lambda x: None)
440
441 @mock.patch('imaplib._MAXLINE', 10)
442 def test_linetoolong(self):
443 class TooLongHandler(SimpleIMAPHandler):
444 def handle(self):
445 # send response line longer than the limit set in the next line
446 self.wfile.write(b'* OK ' + 11 * b'x' + b'\r\n')
447 _, server = self._setup(TooLongHandler, connect=False)
448 with self.assertRaisesRegex(imaplib.IMAP4.error,
449 'got more than 10 bytes'):
450 self.imap_class(*server.server_address)
451
452 def test_simple_with_statement(self):
453 _, server = self._setup(SimpleIMAPHandler, connect=False)
454 with self.imap_class(*server.server_address):
455 pass
456
Dong-hee Na13a7ee82020-01-08 02:28:10 +0900457 def test_imaplib_timeout_test(self):
458 _, server = self._setup(SimpleIMAPHandler)
459 addr = server.server_address[1]
460 client = self.imap_class("localhost", addr, timeout=None)
461 self.assertEqual(client.sock.timeout, None)
462 client.shutdown()
463 client = self.imap_class("localhost", addr, timeout=support.LOOPBACK_TIMEOUT)
464 self.assertEqual(client.sock.timeout, support.LOOPBACK_TIMEOUT)
465 client.shutdown()
466 with self.assertRaises(ValueError):
467 client = self.imap_class("localhost", addr, timeout=0)
468
469 def test_imaplib_timeout_functionality_test(self):
470 class TimeoutHandler(SimpleIMAPHandler):
471 def handle(self):
472 time.sleep(1)
473 SimpleIMAPHandler.handle(self)
474
475 _, server = self._setup(TimeoutHandler)
476 addr = server.server_address[1]
477 with self.assertRaises(socket.timeout):
478 client = self.imap_class("localhost", addr, timeout=0.001)
479
R David Murrayb079c072016-12-24 21:32:26 -0500480 def test_with_statement(self):
481 _, server = self._setup(SimpleIMAPHandler, connect=False)
482 with self.imap_class(*server.server_address) as imap:
483 imap.login('user', 'pass')
484 self.assertEqual(server.logged, 'user')
485 self.assertIsNone(server.logged)
486
487 def test_with_statement_logout(self):
488 # It is legal to log out explicitly inside the with block
489 _, server = self._setup(SimpleIMAPHandler, connect=False)
490 with self.imap_class(*server.server_address) as imap:
491 imap.login('user', 'pass')
492 self.assertEqual(server.logged, 'user')
493 imap.logout()
494 self.assertIsNone(server.logged)
495 self.assertIsNone(server.logged)
496
497 # command tests
498
499 def test_login(self):
500 client, _ = self._setup(SimpleIMAPHandler)
501 typ, data = client.login('user', 'pass')
502 self.assertEqual(typ, 'OK')
503 self.assertEqual(data[0], b'LOGIN completed')
504 self.assertEqual(client.state, 'AUTH')
505
506 def test_logout(self):
507 client, _ = self._setup(SimpleIMAPHandler)
508 typ, data = client.login('user', 'pass')
509 self.assertEqual(typ, 'OK')
510 self.assertEqual(data[0], b'LOGIN completed')
511 typ, data = client.logout()
Victor Stinner74125a62019-04-15 18:23:20 +0200512 self.assertEqual(typ, 'BYE', (typ, data))
513 self.assertEqual(data[0], b'IMAP4ref1 Server logging out', (typ, data))
R David Murrayb079c072016-12-24 21:32:26 -0500514 self.assertEqual(client.state, 'LOGOUT')
515
516 def test_lsub(self):
517 class LsubCmd(SimpleIMAPHandler):
518 def cmd_LSUB(self, tag, args):
519 self._send_textline('* LSUB () "." directoryA')
520 return self._send_tagged(tag, 'OK', 'LSUB completed')
521 client, _ = self._setup(LsubCmd)
522 client.login('user', 'pass')
523 typ, data = client.lsub()
524 self.assertEqual(typ, 'OK')
525 self.assertEqual(data[0], b'() "." directoryA')
526
Dong-hee Nac5c42812020-04-27 23:52:55 +0900527 def test_unselect(self):
528 client, _ = self._setup(SimpleIMAPHandler)
529 client.login('user', 'pass')
530 typ, data = client.select()
531 self.assertEqual(typ, 'OK')
532 self.assertEqual(data[0], b'2')
533
534 typ, data = client.unselect()
535 self.assertEqual(typ, 'OK')
536 self.assertEqual(data[0], b'Returned to authenticated state. (Success)')
537 self.assertEqual(client.state, 'AUTH')
538
R David Murrayb079c072016-12-24 21:32:26 -0500539
540class NewIMAPTests(NewIMAPTestsMixin, unittest.TestCase):
541 imap_class = imaplib.IMAP4
542 server_class = socketserver.TCPServer
543
544
545@unittest.skipUnless(ssl, "SSL not available")
546class NewIMAPSSLTests(NewIMAPTestsMixin, unittest.TestCase):
Victor Stinnerde383282017-01-12 11:51:31 +0100547 imap_class = IMAP4_SSL
R David Murrayb079c072016-12-24 21:32:26 -0500548 server_class = SecureTCPServer
549
550 def test_ssl_raises(self):
Christian Heimesa170fa12017-09-15 20:27:30 +0200551 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
552 self.assertEqual(ssl_context.verify_mode, ssl.CERT_REQUIRED)
553 self.assertEqual(ssl_context.check_hostname, True)
R David Murrayb079c072016-12-24 21:32:26 -0500554 ssl_context.load_verify_locations(CAFILE)
555
556 with self.assertRaisesRegex(ssl.CertificateError,
Christian Heimes61d478c2018-01-27 15:51:38 +0100557 "IP address mismatch, certificate is not valid for "
558 "'127.0.0.1'"):
R David Murrayb079c072016-12-24 21:32:26 -0500559 _, server = self._setup(SimpleIMAPHandler)
560 client = self.imap_class(*server.server_address,
561 ssl_context=ssl_context)
562 client.shutdown()
563
564 def test_ssl_verified(self):
Christian Heimesa170fa12017-09-15 20:27:30 +0200565 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
R David Murrayb079c072016-12-24 21:32:26 -0500566 ssl_context.load_verify_locations(CAFILE)
567
568 _, server = self._setup(SimpleIMAPHandler)
569 client = self.imap_class("localhost", server.server_address[1],
570 ssl_context=ssl_context)
571 client.shutdown()
572
Victor Stinnerb18563d2017-06-14 18:48:32 +0200573 # Mock the private method _connect(), so mark the test as specific
574 # to CPython stdlib
575 @cpython_only
576 def test_certfile_arg_warn(self):
577 with support.check_warnings(('', DeprecationWarning)):
578 with mock.patch.object(self.imap_class, 'open'):
579 with mock.patch.object(self.imap_class, '_connect'):
580 self.imap_class('localhost', 143, certfile=CERTFILE)
581
Antoine Pitroucac9e712014-07-31 18:35:45 -0400582class ThreadedNetworkedTests(unittest.TestCase):
583 server_class = socketserver.TCPServer
584 imap_class = imaplib.IMAP4
R. David Murraye8dc2582009-12-10 02:08:06 +0000585
586 def make_server(self, addr, hdlr):
587
588 class MyServer(self.server_class):
589 def handle_error(self, request, client_address):
590 self.close_request(request)
591 self.server_close()
592 raise
593
Antoine Pitroucac9e712014-07-31 18:35:45 -0400594 if verbose:
595 print("creating server")
R. David Murraye8dc2582009-12-10 02:08:06 +0000596 server = MyServer(addr, hdlr)
Ezio Melottib3aedd42010-11-20 19:04:17 +0000597 self.assertEqual(server.server_address, server.socket.getsockname())
R. David Murraye8dc2582009-12-10 02:08:06 +0000598
599 if verbose:
600 print("server created")
601 print("ADDR =", addr)
602 print("CLASS =", self.server_class)
603 print("HDLR =", server.RequestHandlerClass)
604
605 t = threading.Thread(
606 name='%s serving' % self.server_class,
607 target=server.serve_forever,
608 # Short poll interval to make the test finish quickly.
609 # Time between requests is short enough that we won't wake
610 # up spuriously too many times.
Antoine Pitroucac9e712014-07-31 18:35:45 -0400611 kwargs={'poll_interval': 0.01})
R. David Murraye8dc2582009-12-10 02:08:06 +0000612 t.daemon = True # In case this function raises.
613 t.start()
Antoine Pitroucac9e712014-07-31 18:35:45 -0400614 if verbose:
615 print("server running")
R. David Murraye8dc2582009-12-10 02:08:06 +0000616 return server, t
617
618 def reap_server(self, server, thread):
Antoine Pitroucac9e712014-07-31 18:35:45 -0400619 if verbose:
620 print("waiting for server")
R. David Murraye8dc2582009-12-10 02:08:06 +0000621 server.shutdown()
Victor Stinner73efd622011-01-05 23:01:38 +0000622 server.server_close()
R. David Murraye8dc2582009-12-10 02:08:06 +0000623 thread.join()
Antoine Pitroucac9e712014-07-31 18:35:45 -0400624 if verbose:
625 print("done")
R. David Murraye8dc2582009-12-10 02:08:06 +0000626
627 @contextmanager
628 def reaped_server(self, hdlr):
Serhiy Storchaka16994912020-04-25 10:06:29 +0300629 server, thread = self.make_server((socket_helper.HOST, 0), hdlr)
R. David Murraye8dc2582009-12-10 02:08:06 +0000630 try:
631 yield server
632 finally:
633 self.reap_server(server, thread)
634
R David Murray774a39f2013-02-19 12:17:31 -0500635 @contextmanager
636 def reaped_pair(self, hdlr):
Charles-François Natali9b116e82013-12-07 20:27:41 +0100637 with self.reaped_server(hdlr) as server:
638 client = self.imap_class(*server.server_address)
639 try:
640 yield server, client
641 finally:
642 client.logout()
R David Murray774a39f2013-02-19 12:17:31 -0500643
R. David Murraye8dc2582009-12-10 02:08:06 +0000644 @reap_threads
645 def test_connect(self):
646 with self.reaped_server(SimpleIMAPHandler) as server:
647 client = self.imap_class(*server.server_address)
648 client.shutdown()
649
650 @reap_threads
R David Murray317f64f2016-01-02 17:18:34 -0500651 def test_bracket_flags(self):
652
653 # This violates RFC 3501, which disallows ']' characters in tag names,
654 # but imaplib has allowed producing such tags forever, other programs
655 # also produce them (eg: OtherInbox's Organizer app as of 20140716),
656 # and Gmail, for example, accepts them and produces them. So we
657 # support them. See issue #21815.
658
659 class BracketFlagHandler(SimpleIMAPHandler):
660
661 def handle(self):
662 self.flags = ['Answered', 'Flagged', 'Deleted', 'Seen', 'Draft']
663 super().handle()
664
665 def cmd_AUTHENTICATE(self, tag, args):
666 self._send_textline('+')
667 self.server.response = yield
668 self._send_tagged(tag, 'OK', 'FAKEAUTH successful')
669
670 def cmd_SELECT(self, tag, args):
671 flag_msg = ' \\'.join(self.flags)
672 self._send_line(('* FLAGS (%s)' % flag_msg).encode('ascii'))
673 self._send_line(b'* 2 EXISTS')
674 self._send_line(b'* 0 RECENT')
675 msg = ('* OK [PERMANENTFLAGS %s \\*)] Flags permitted.'
676 % flag_msg)
677 self._send_line(msg.encode('ascii'))
678 self._send_tagged(tag, 'OK', '[READ-WRITE] SELECT completed.')
679
680 def cmd_STORE(self, tag, args):
681 new_flags = args[2].strip('(').strip(')').split()
682 self.flags.extend(new_flags)
683 flags_msg = '(FLAGS (%s))' % ' \\'.join(self.flags)
684 msg = '* %s FETCH %s' % (args[0], flags_msg)
685 self._send_line(msg.encode('ascii'))
686 self._send_tagged(tag, 'OK', 'STORE completed.')
687
688 with self.reaped_pair(BracketFlagHandler) as (server, client):
689 code, data = client.authenticate('MYAUTH', lambda x: b'fake')
690 self.assertEqual(code, 'OK')
691 self.assertEqual(server.response, b'ZmFrZQ==\r\n')
692 client.select('test')
693 typ, [data] = client.store(b'1', "+FLAGS", "[test]")
694 self.assertIn(b'[test]', data)
695 client.select('test')
696 typ, [data] = client.response('PERMANENTFLAGS')
697 self.assertIn(b'[test]', data)
698
699 @reap_threads
R. David Murraye8dc2582009-12-10 02:08:06 +0000700 def test_issue5949(self):
701
702 class EOFHandler(socketserver.StreamRequestHandler):
703 def handle(self):
704 # EOF without sending a complete welcome message.
705 self.wfile.write(b'* OK')
706
707 with self.reaped_server(EOFHandler) as server:
708 self.assertRaises(imaplib.IMAP4.abort,
709 self.imap_class, *server.server_address)
710
711 @reap_threads
712 def test_line_termination(self):
713
714 class BadNewlineHandler(SimpleIMAPHandler):
715
716 def cmd_CAPABILITY(self, tag, args):
717 self._send(b'* CAPABILITY IMAP4rev1 AUTH\n')
R David Murray774a39f2013-02-19 12:17:31 -0500718 self._send_tagged(tag, 'OK', 'CAPABILITY completed')
R. David Murraye8dc2582009-12-10 02:08:06 +0000719
720 with self.reaped_server(BadNewlineHandler) as server:
721 self.assertRaises(imaplib.IMAP4.abort,
722 self.imap_class, *server.server_address)
723
R David Murraya6429db2015-05-10 19:17:23 -0400724 class UTF8Server(SimpleIMAPHandler):
725 capabilities = 'AUTH ENABLE UTF8=ACCEPT'
726
727 def cmd_ENABLE(self, tag, args):
728 self._send_tagged(tag, 'OK', 'ENABLE successful')
729
730 def cmd_AUTHENTICATE(self, tag, args):
731 self._send_textline('+')
732 self.server.response = yield
733 self._send_tagged(tag, 'OK', 'FAKEAUTH successful')
734
735 @reap_threads
736 def test_enable_raises_error_if_not_AUTH(self):
737 with self.reaped_pair(self.UTF8Server) as (server, client):
738 self.assertFalse(client.utf8_enabled)
739 self.assertRaises(imaplib.IMAP4.error, client.enable, 'foo')
740 self.assertFalse(client.utf8_enabled)
741
742 # XXX Also need a test that enable after SELECT raises an error.
743
744 @reap_threads
745 def test_enable_raises_error_if_no_capability(self):
746 class NoEnableServer(self.UTF8Server):
747 capabilities = 'AUTH'
748 with self.reaped_pair(NoEnableServer) as (server, client):
749 self.assertRaises(imaplib.IMAP4.error, client.enable, 'foo')
750
751 @reap_threads
752 def test_enable_UTF8_raises_error_if_not_supported(self):
753 class NonUTF8Server(SimpleIMAPHandler):
754 pass
755 with self.assertRaises(imaplib.IMAP4.error):
756 with self.reaped_pair(NonUTF8Server) as (server, client):
757 typ, data = client.login('user', 'pass')
758 self.assertEqual(typ, 'OK')
759 client.enable('UTF8=ACCEPT')
760 pass
761
762 @reap_threads
763 def test_enable_UTF8_True_append(self):
764
765 class UTF8AppendServer(self.UTF8Server):
766 def cmd_APPEND(self, tag, args):
767 self._send_textline('+')
768 self.server.response = yield
769 self._send_tagged(tag, 'OK', 'okay')
770
771 with self.reaped_pair(UTF8AppendServer) as (server, client):
772 self.assertEqual(client._encoding, 'ascii')
773 code, _ = client.authenticate('MYAUTH', lambda x: b'fake')
774 self.assertEqual(code, 'OK')
775 self.assertEqual(server.response,
776 b'ZmFrZQ==\r\n') # b64 encoded 'fake'
777 code, _ = client.enable('UTF8=ACCEPT')
778 self.assertEqual(code, 'OK')
779 self.assertEqual(client._encoding, 'utf-8')
780 msg_string = 'Subject: üñí©öðé'
781 typ, data = client.append(
782 None, None, None, msg_string.encode('utf-8'))
783 self.assertEqual(typ, 'OK')
784 self.assertEqual(
785 server.response,
786 ('UTF8 (%s)\r\n' % msg_string).encode('utf-8')
787 )
788
789 # XXX also need a test that makes sure that the Literal and Untagged_status
790 # regexes uses unicode in UTF8 mode instead of the default ASCII.
791
792 @reap_threads
793 def test_search_disallows_charset_in_utf8_mode(self):
794 with self.reaped_pair(self.UTF8Server) as (server, client):
795 typ, _ = client.authenticate('MYAUTH', lambda x: b'fake')
796 self.assertEqual(typ, 'OK')
797 typ, _ = client.enable('UTF8=ACCEPT')
798 self.assertEqual(typ, 'OK')
799 self.assertTrue(client.utf8_enabled)
800 self.assertRaises(imaplib.IMAP4.error, client.search, 'foo', 'bar')
801
R David Murray774a39f2013-02-19 12:17:31 -0500802 @reap_threads
803 def test_bad_auth_name(self):
804
805 class MyServer(SimpleIMAPHandler):
806
807 def cmd_AUTHENTICATE(self, tag, args):
808 self._send_tagged(tag, 'NO', 'unrecognized authentication '
Antoine Pitroucac9e712014-07-31 18:35:45 -0400809 'type {}'.format(args[0]))
R David Murray774a39f2013-02-19 12:17:31 -0500810
811 with self.reaped_pair(MyServer) as (server, client):
812 with self.assertRaises(imaplib.IMAP4.error):
813 client.authenticate('METHOD', lambda: 1)
814
815 @reap_threads
816 def test_invalid_authentication(self):
817
818 class MyServer(SimpleIMAPHandler):
819
820 def cmd_AUTHENTICATE(self, tag, args):
821 self._send_textline('+')
822 self.response = yield
823 self._send_tagged(tag, 'NO', '[AUTHENTICATIONFAILED] invalid')
824
825 with self.reaped_pair(MyServer) as (server, client):
826 with self.assertRaises(imaplib.IMAP4.error):
827 code, data = client.authenticate('MYAUTH', lambda x: b'fake')
828
829 @reap_threads
830 def test_valid_authentication(self):
831
832 class MyServer(SimpleIMAPHandler):
833
834 def cmd_AUTHENTICATE(self, tag, args):
835 self._send_textline('+')
836 self.server.response = yield
837 self._send_tagged(tag, 'OK', 'FAKEAUTH successful')
838
839 with self.reaped_pair(MyServer) as (server, client):
840 code, data = client.authenticate('MYAUTH', lambda x: b'fake')
841 self.assertEqual(code, 'OK')
842 self.assertEqual(server.response,
Antoine Pitroucac9e712014-07-31 18:35:45 -0400843 b'ZmFrZQ==\r\n') # b64 encoded 'fake'
R David Murray774a39f2013-02-19 12:17:31 -0500844
845 with self.reaped_pair(MyServer) as (server, client):
846 code, data = client.authenticate('MYAUTH', lambda x: 'fake')
847 self.assertEqual(code, 'OK')
848 self.assertEqual(server.response,
Antoine Pitroucac9e712014-07-31 18:35:45 -0400849 b'ZmFrZQ==\r\n') # b64 encoded 'fake'
R David Murray774a39f2013-02-19 12:17:31 -0500850
851 @reap_threads
Christian Heimesc64a1a62019-09-25 16:30:20 +0200852 @requires_hashdigest('md5')
R David Murray774a39f2013-02-19 12:17:31 -0500853 def test_login_cram_md5(self):
854
855 class AuthHandler(SimpleIMAPHandler):
856
857 capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
858
859 def cmd_AUTHENTICATE(self, tag, args):
860 self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
Antoine Pitroucac9e712014-07-31 18:35:45 -0400861 'VzdG9uLm1jaS5uZXQ=')
R David Murray774a39f2013-02-19 12:17:31 -0500862 r = yield
Antoine Pitroucac9e712014-07-31 18:35:45 -0400863 if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
864 b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
R David Murray774a39f2013-02-19 12:17:31 -0500865 self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
866 else:
867 self._send_tagged(tag, 'NO', 'No access')
868
869 with self.reaped_pair(AuthHandler) as (server, client):
870 self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
871 ret, data = client.login_cram_md5("tim", "tanstaaftanstaaf")
872 self.assertEqual(ret, "OK")
873
874 with self.reaped_pair(AuthHandler) as (server, client):
875 self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
876 ret, data = client.login_cram_md5("tim", b"tanstaaftanstaaf")
877 self.assertEqual(ret, "OK")
R. David Murraye8dc2582009-12-10 02:08:06 +0000878
879
Robert Collins5ccc18f2015-07-31 08:59:02 +1200880 @reap_threads
881 def test_aborted_authentication(self):
882
883 class MyServer(SimpleIMAPHandler):
884
885 def cmd_AUTHENTICATE(self, tag, args):
886 self._send_textline('+')
887 self.response = yield
888
889 if self.response == b'*\r\n':
890 self._send_tagged(tag, 'NO', '[AUTHENTICATIONFAILED] aborted')
891 else:
892 self._send_tagged(tag, 'OK', 'MYAUTH successful')
893
894 with self.reaped_pair(MyServer) as (server, client):
895 with self.assertRaises(imaplib.IMAP4.error):
896 code, data = client.authenticate('MYAUTH', lambda x: None)
897
Robert Collins78378e82015-07-31 09:01:38 +1200898
Georg Brandlca580f42013-10-27 06:52:14 +0100899 def test_linetoolong(self):
900 class TooLongHandler(SimpleIMAPHandler):
901 def handle(self):
902 # Send a very long response line
Antoine Pitroucac9e712014-07-31 18:35:45 -0400903 self.wfile.write(b'* OK ' + imaplib._MAXLINE * b'x' + b'\r\n')
Georg Brandlca580f42013-10-27 06:52:14 +0100904
905 with self.reaped_server(TooLongHandler) as server:
906 self.assertRaises(imaplib.IMAP4.error,
907 self.imap_class, *server.server_address)
908
Serhiy Storchaka38684c32014-09-09 19:07:49 +0300909 @reap_threads
910 def test_simple_with_statement(self):
911 # simplest call
912 with self.reaped_server(SimpleIMAPHandler) as server:
913 with self.imap_class(*server.server_address):
914 pass
915
916 @reap_threads
917 def test_with_statement(self):
918 with self.reaped_server(SimpleIMAPHandler) as server:
919 with self.imap_class(*server.server_address) as imap:
920 imap.login('user', 'pass')
921 self.assertEqual(server.logged, 'user')
922 self.assertIsNone(server.logged)
923
924 @reap_threads
925 def test_with_statement_logout(self):
926 # what happens if already logout in the block?
927 with self.reaped_server(SimpleIMAPHandler) as server:
928 with self.imap_class(*server.server_address) as imap:
929 imap.login('user', 'pass')
930 self.assertEqual(server.logged, 'user')
931 imap.logout()
932 self.assertIsNone(server.logged)
933 self.assertIsNone(server.logged)
934
Georg Brandlca580f42013-10-27 06:52:14 +0100935
R. David Murraye8dc2582009-12-10 02:08:06 +0000936@unittest.skipUnless(ssl, "SSL not available")
Antoine Pitroucac9e712014-07-31 18:35:45 -0400937class ThreadedNetworkedTestsSSL(ThreadedNetworkedTests):
R. David Murraye8dc2582009-12-10 02:08:06 +0000938 server_class = SecureTCPServer
939 imap_class = IMAP4_SSL
940
Christian Heimes48aae572013-12-02 20:01:29 +0100941 @reap_threads
942 def test_ssl_verified(self):
Christian Heimesa170fa12017-09-15 20:27:30 +0200943 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
Christian Heimes48aae572013-12-02 20:01:29 +0100944 ssl_context.load_verify_locations(CAFILE)
945
Antoine Pitroucac9e712014-07-31 18:35:45 -0400946 with self.assertRaisesRegex(
947 ssl.CertificateError,
Christian Heimes61d478c2018-01-27 15:51:38 +0100948 "IP address mismatch, certificate is not valid for "
949 "'127.0.0.1'"):
Christian Heimes48aae572013-12-02 20:01:29 +0100950 with self.reaped_server(SimpleIMAPHandler) as server:
951 client = self.imap_class(*server.server_address,
952 ssl_context=ssl_context)
953 client.shutdown()
954
955 with self.reaped_server(SimpleIMAPHandler) as server:
956 client = self.imap_class("localhost", server.server_address[1],
957 ssl_context=ssl_context)
958 client.shutdown()
959
R. David Murraye8dc2582009-12-10 02:08:06 +0000960
Antoine Pitroucac9e712014-07-31 18:35:45 -0400961@unittest.skipUnless(
962 support.is_resource_enabled('network'), 'network resource disabled')
Antoine Pitroub1436f12010-11-09 22:55:55 +0000963class RemoteIMAPTest(unittest.TestCase):
964 host = 'cyrus.andrew.cmu.edu'
965 port = 143
966 username = 'anonymous'
967 password = 'pass'
968 imap_class = imaplib.IMAP4
R. David Murraye8dc2582009-12-10 02:08:06 +0000969
Antoine Pitroub1436f12010-11-09 22:55:55 +0000970 def setUp(self):
971 with transient_internet(self.host):
972 self.server = self.imap_class(self.host, self.port)
973
974 def tearDown(self):
975 if self.server is not None:
Antoine Pitrou924cbea2011-03-23 03:10:14 +0100976 with transient_internet(self.host):
977 self.server.logout()
Antoine Pitroub1436f12010-11-09 22:55:55 +0000978
979 def test_logincapa(self):
Antoine Pitrou924cbea2011-03-23 03:10:14 +0100980 with transient_internet(self.host):
981 for cap in self.server.capabilities:
982 self.assertIsInstance(cap, str)
Nick Coghlane6ef4622012-06-17 21:10:21 +1000983 self.assertIn('LOGINDISABLED', self.server.capabilities)
984 self.assertIn('AUTH=ANONYMOUS', self.server.capabilities)
Antoine Pitrou924cbea2011-03-23 03:10:14 +0100985 rs = self.server.login(self.username, self.password)
986 self.assertEqual(rs[0], 'OK')
Antoine Pitroub1436f12010-11-09 22:55:55 +0000987
988 def test_logout(self):
Antoine Pitrou924cbea2011-03-23 03:10:14 +0100989 with transient_internet(self.host):
990 rs = self.server.logout()
991 self.server = None
Victor Stinner74125a62019-04-15 18:23:20 +0200992 self.assertEqual(rs[0], 'BYE', rs)
Antoine Pitroub1436f12010-11-09 22:55:55 +0000993
994
995@unittest.skipUnless(ssl, "SSL not available")
Antoine Pitroucac9e712014-07-31 18:35:45 -0400996@unittest.skipUnless(
997 support.is_resource_enabled('network'), 'network resource disabled')
Antoine Pitrouf3b001f2010-11-12 18:49:16 +0000998class RemoteIMAP_STARTTLSTest(RemoteIMAPTest):
999
1000 def setUp(self):
1001 super().setUp()
Antoine Pitrou924cbea2011-03-23 03:10:14 +01001002 with transient_internet(self.host):
1003 rs = self.server.starttls()
1004 self.assertEqual(rs[0], 'OK')
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001005
1006 def test_logincapa(self):
Antoine Pitroudbe75192010-11-16 17:55:26 +00001007 for cap in self.server.capabilities:
1008 self.assertIsInstance(cap, str)
Nick Coghlane6ef4622012-06-17 21:10:21 +10001009 self.assertNotIn('LOGINDISABLED', self.server.capabilities)
Antoine Pitrouf3b001f2010-11-12 18:49:16 +00001010
1011
1012@unittest.skipUnless(ssl, "SSL not available")
Antoine Pitroub1436f12010-11-09 22:55:55 +00001013class RemoteIMAP_SSLTest(RemoteIMAPTest):
1014 port = 993
1015 imap_class = IMAP4_SSL
1016
Antoine Pitrou08728162011-05-06 18:49:52 +02001017 def setUp(self):
1018 pass
1019
1020 def tearDown(self):
1021 pass
1022
1023 def create_ssl_context(self):
Christian Heimesa170fa12017-09-15 20:27:30 +02001024 ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
1025 ssl_context.check_hostname = False
1026 ssl_context.verify_mode = ssl.CERT_NONE
Antoine Pitrou08728162011-05-06 18:49:52 +02001027 ssl_context.load_cert_chain(CERTFILE)
1028 return ssl_context
1029
1030 def check_logincapa(self, server):
1031 try:
1032 for cap in server.capabilities:
1033 self.assertIsInstance(cap, str)
Nick Coghlane51e25a2012-06-17 21:15:45 +10001034 self.assertNotIn('LOGINDISABLED', server.capabilities)
1035 self.assertIn('AUTH=PLAIN', server.capabilities)
Antoine Pitrou08728162011-05-06 18:49:52 +02001036 rs = server.login(self.username, self.password)
1037 self.assertEqual(rs[0], 'OK')
1038 finally:
1039 server.logout()
1040
Antoine Pitroub1436f12010-11-09 22:55:55 +00001041 def test_logincapa(self):
Antoine Pitrou08728162011-05-06 18:49:52 +02001042 with transient_internet(self.host):
1043 _server = self.imap_class(self.host, self.port)
1044 self.check_logincapa(_server)
1045
Antoine Pitrou08728162011-05-06 18:49:52 +02001046 def test_logout(self):
1047 with transient_internet(self.host):
1048 _server = self.imap_class(self.host, self.port)
1049 rs = _server.logout()
Victor Stinner74125a62019-04-15 18:23:20 +02001050 self.assertEqual(rs[0], 'BYE', rs)
Antoine Pitrou08728162011-05-06 18:49:52 +02001051
1052 def test_ssl_context_certfile_exclusive(self):
1053 with transient_internet(self.host):
Antoine Pitroucac9e712014-07-31 18:35:45 -04001054 self.assertRaises(
1055 ValueError, self.imap_class, self.host, self.port,
1056 certfile=CERTFILE, ssl_context=self.create_ssl_context())
Antoine Pitrou08728162011-05-06 18:49:52 +02001057
1058 def test_ssl_context_keyfile_exclusive(self):
1059 with transient_internet(self.host):
Antoine Pitroucac9e712014-07-31 18:35:45 -04001060 self.assertRaises(
1061 ValueError, self.imap_class, self.host, self.port,
1062 keyfile=CERTFILE, ssl_context=self.create_ssl_context())
Christian Heimesf6cd9672008-03-26 13:45:42 +00001063
1064
1065if __name__ == "__main__":
Ezio Melotti02bf7012013-03-02 14:25:56 +02001066 unittest.main()