| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | import io | 
					
						
							| 
									
										
										
										
											2011-03-03 18:34:06 +00:00
										 |  |  | import socket | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | import datetime | 
					
						
							|  |  |  | import textwrap | 
					
						
							|  |  |  | import unittest | 
					
						
							| 
									
										
										
										
											2010-11-18 17:29:23 +00:00
										 |  |  | import functools | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | import contextlib | 
					
						
							| 
									
										
										
										
											2020-05-16 19:31:54 +09:00
										 |  |  | import nntplib | 
					
						
							| 
									
										
										
										
											2016-01-19 01:10:58 +00:00
										 |  |  | import os.path | 
					
						
							| 
									
										
										
										
											2019-05-06 17:54:06 -04:00
										 |  |  | import re | 
					
						
							| 
									
										
										
										
											2017-09-07 18:56:24 +02:00
										 |  |  | import threading | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | from test import support | 
					
						
							| 
									
										
										
										
											2020-04-25 10:06:29 +03:00
										 |  |  | from test.support import socket_helper | 
					
						
							| 
									
										
										
										
											2013-11-03 21:31:38 +02:00
										 |  |  | from nntplib import NNTP, GroupInfo | 
					
						
							| 
									
										
										
										
											2015-03-21 09:40:26 +02:00
										 |  |  | from unittest.mock import patch | 
					
						
							| 
									
										
										
										
											2013-11-03 21:31:38 +02:00
										 |  |  | try: | 
					
						
							| 
									
										
										
										
											2010-11-09 18:54:37 +00:00
										 |  |  |     import ssl | 
					
						
							| 
									
										
										
										
											2013-11-03 21:31:38 +02:00
										 |  |  | except ImportError: | 
					
						
							|  |  |  |     ssl = None | 
					
						
							| 
									
										
										
										
											2017-09-07 18:56:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-19 01:10:58 +00:00
										 |  |  | certfile = os.path.join(os.path.dirname(__file__), 'keycert3.pem') | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-06 17:54:06 -04:00
										 |  |  | if ssl is not None: | 
					
						
							|  |  |  |     SSLError = ssl.SSLError | 
					
						
							|  |  |  | else: | 
					
						
							|  |  |  |     class SSLError(Exception): | 
					
						
							|  |  |  |         """Non-existent exception class when we lack SSL support.""" | 
					
						
							|  |  |  |         reason = "This will never be raised." | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | # TODO: | 
					
						
							|  |  |  | # - test the `file` arg to more commands | 
					
						
							|  |  |  | # - test error conditions | 
					
						
							| 
									
										
										
										
											2010-09-29 16:19:50 +00:00
										 |  |  | # - test auth and `usenetrc` | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class NetworkedNNTPTestsMixin: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_welcome(self): | 
					
						
							|  |  |  |         welcome = self.server.getwelcome() | 
					
						
							|  |  |  |         self.assertEqual(str, type(welcome)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_help(self): | 
					
						
							| 
									
										
										
										
											2010-11-04 21:36:15 +00:00
										 |  |  |         resp, lines = self.server.help() | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         self.assertTrue(resp.startswith("100 "), resp) | 
					
						
							| 
									
										
										
										
											2010-11-04 21:36:15 +00:00
										 |  |  |         for line in lines: | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |             self.assertEqual(str, type(line)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_list(self): | 
					
						
							| 
									
										
										
										
											2010-11-04 21:36:15 +00:00
										 |  |  |         resp, groups = self.server.list() | 
					
						
							|  |  |  |         if len(groups) > 0: | 
					
						
							|  |  |  |             self.assertEqual(GroupInfo, type(groups[0])) | 
					
						
							|  |  |  |             self.assertEqual(str, type(groups[0].group)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_list_active(self): | 
					
						
							|  |  |  |         resp, groups = self.server.list(self.GROUP_PAT) | 
					
						
							|  |  |  |         if len(groups) > 0: | 
					
						
							|  |  |  |             self.assertEqual(GroupInfo, type(groups[0])) | 
					
						
							|  |  |  |             self.assertEqual(str, type(groups[0].group)) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_unknown_command(self): | 
					
						
							|  |  |  |         with self.assertRaises(nntplib.NNTPPermanentError) as cm: | 
					
						
							|  |  |  |             self.server._shortcmd("XYZZY") | 
					
						
							|  |  |  |         resp = cm.exception.response | 
					
						
							|  |  |  |         self.assertTrue(resp.startswith("500 "), resp) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_newgroups(self): | 
					
						
							|  |  |  |         # gmane gets a constant influx of new groups.  In order not to stress | 
					
						
							|  |  |  |         # the server too much, we choose a recent date in the past. | 
					
						
							|  |  |  |         dt = datetime.date.today() - datetime.timedelta(days=7) | 
					
						
							|  |  |  |         resp, groups = self.server.newgroups(dt) | 
					
						
							|  |  |  |         if len(groups) > 0: | 
					
						
							|  |  |  |             self.assertIsInstance(groups[0], GroupInfo) | 
					
						
							|  |  |  |             self.assertIsInstance(groups[0].group, str) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_description(self): | 
					
						
							|  |  |  |         def _check_desc(desc): | 
					
						
							|  |  |  |             # Sanity checks | 
					
						
							|  |  |  |             self.assertIsInstance(desc, str) | 
					
						
							|  |  |  |             self.assertNotIn(self.GROUP_NAME, desc) | 
					
						
							|  |  |  |         desc = self.server.description(self.GROUP_NAME) | 
					
						
							|  |  |  |         _check_desc(desc) | 
					
						
							|  |  |  |         # Another sanity check | 
					
						
							|  |  |  |         self.assertIn("Python", desc) | 
					
						
							|  |  |  |         # With a pattern | 
					
						
							|  |  |  |         desc = self.server.description(self.GROUP_PAT) | 
					
						
							|  |  |  |         _check_desc(desc) | 
					
						
							|  |  |  |         # Shouldn't exist | 
					
						
							|  |  |  |         desc = self.server.description("zk.brrtt.baz") | 
					
						
							|  |  |  |         self.assertEqual(desc, '') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_descriptions(self): | 
					
						
							|  |  |  |         resp, descs = self.server.descriptions(self.GROUP_PAT) | 
					
						
							|  |  |  |         # 215 for LIST NEWSGROUPS, 282 for XGTITLE | 
					
						
							|  |  |  |         self.assertTrue( | 
					
						
							|  |  |  |             resp.startswith("215 ") or resp.startswith("282 "), resp) | 
					
						
							|  |  |  |         self.assertIsInstance(descs, dict) | 
					
						
							|  |  |  |         desc = descs[self.GROUP_NAME] | 
					
						
							|  |  |  |         self.assertEqual(desc, self.server.description(self.GROUP_NAME)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_group(self): | 
					
						
							|  |  |  |         result = self.server.group(self.GROUP_NAME) | 
					
						
							|  |  |  |         self.assertEqual(5, len(result)) | 
					
						
							|  |  |  |         resp, count, first, last, group = result | 
					
						
							|  |  |  |         self.assertEqual(group, self.GROUP_NAME) | 
					
						
							|  |  |  |         self.assertIsInstance(count, int) | 
					
						
							|  |  |  |         self.assertIsInstance(first, int) | 
					
						
							|  |  |  |         self.assertIsInstance(last, int) | 
					
						
							|  |  |  |         self.assertLessEqual(first, last) | 
					
						
							|  |  |  |         self.assertTrue(resp.startswith("211 "), resp) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_date(self): | 
					
						
							|  |  |  |         resp, date = self.server.date() | 
					
						
							|  |  |  |         self.assertIsInstance(date, datetime.datetime) | 
					
						
							|  |  |  |         # Sanity check | 
					
						
							|  |  |  |         self.assertGreaterEqual(date.year, 1995) | 
					
						
							|  |  |  |         self.assertLessEqual(date.year, 2030) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _check_art_dict(self, art_dict): | 
					
						
							|  |  |  |         # Some sanity checks for a field dictionary returned by OVER / XOVER | 
					
						
							|  |  |  |         self.assertIsInstance(art_dict, dict) | 
					
						
							|  |  |  |         # NNTP has 7 mandatory fields | 
					
						
							|  |  |  |         self.assertGreaterEqual(art_dict.keys(), | 
					
						
							|  |  |  |             {"subject", "from", "date", "message-id", | 
					
						
							|  |  |  |              "references", ":bytes", ":lines"} | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         for v in art_dict.values(): | 
					
						
							| 
									
										
										
										
											2010-11-09 18:54:37 +00:00
										 |  |  |             self.assertIsInstance(v, (str, type(None))) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_xover(self): | 
					
						
							|  |  |  |         resp, count, first, last, name = self.server.group(self.GROUP_NAME) | 
					
						
							| 
									
										
										
										
											2010-11-18 15:11:43 +00:00
										 |  |  |         resp, lines = self.server.xover(last - 5, last) | 
					
						
							|  |  |  |         if len(lines) == 0: | 
					
						
							|  |  |  |             self.skipTest("no articles retrieved") | 
					
						
							|  |  |  |         # The 'last' article is not necessarily part of the output (cancelled?) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         art_num, art_dict = lines[0] | 
					
						
							| 
									
										
										
										
											2010-11-18 15:11:43 +00:00
										 |  |  |         self.assertGreaterEqual(art_num, last - 5) | 
					
						
							|  |  |  |         self.assertLessEqual(art_num, last) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         self._check_art_dict(art_dict) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-16 20:49:10 +01:00
										 |  |  |     @unittest.skipIf(True, 'temporarily skipped until a permanent solution' | 
					
						
							|  |  |  |                            ' is found for issue #28971') | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |     def test_over(self): | 
					
						
							|  |  |  |         resp, count, first, last, name = self.server.group(self.GROUP_NAME) | 
					
						
							|  |  |  |         start = last - 10 | 
					
						
							|  |  |  |         # The "start-" article range form | 
					
						
							|  |  |  |         resp, lines = self.server.over((start, None)) | 
					
						
							|  |  |  |         art_num, art_dict = lines[0] | 
					
						
							|  |  |  |         self._check_art_dict(art_dict) | 
					
						
							|  |  |  |         # The "start-end" article range form | 
					
						
							|  |  |  |         resp, lines = self.server.over((start, last)) | 
					
						
							|  |  |  |         art_num, art_dict = lines[-1] | 
					
						
							| 
									
										
										
										
											2010-11-18 15:11:43 +00:00
										 |  |  |         # The 'last' article is not necessarily part of the output (cancelled?) | 
					
						
							|  |  |  |         self.assertGreaterEqual(art_num, start) | 
					
						
							|  |  |  |         self.assertLessEqual(art_num, last) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         self._check_art_dict(art_dict) | 
					
						
							|  |  |  |         # XXX The "message_id" form is unsupported by gmane | 
					
						
							|  |  |  |         # 503 Overview by message-ID unsupported | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_xhdr(self): | 
					
						
							|  |  |  |         resp, count, first, last, name = self.server.group(self.GROUP_NAME) | 
					
						
							|  |  |  |         resp, lines = self.server.xhdr('subject', last) | 
					
						
							|  |  |  |         for line in lines: | 
					
						
							|  |  |  |             self.assertEqual(str, type(line[1])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def check_article_resp(self, resp, article, art_num=None): | 
					
						
							|  |  |  |         self.assertIsInstance(article, nntplib.ArticleInfo) | 
					
						
							|  |  |  |         if art_num is not None: | 
					
						
							|  |  |  |             self.assertEqual(article.number, art_num) | 
					
						
							|  |  |  |         for line in article.lines: | 
					
						
							|  |  |  |             self.assertIsInstance(line, bytes) | 
					
						
							|  |  |  |         # XXX this could exceptionally happen... | 
					
						
							|  |  |  |         self.assertNotIn(article.lines[-1], (b".", b".\n", b".\r\n")) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-25 02:42:18 +01:00
										 |  |  |     @unittest.skipIf(True, "FIXME: see bpo-32128") | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |     def test_article_head_body(self): | 
					
						
							|  |  |  |         resp, count, first, last, name = self.server.group(self.GROUP_NAME) | 
					
						
							| 
									
										
										
										
											2010-11-18 15:11:43 +00:00
										 |  |  |         # Try to find an available article | 
					
						
							|  |  |  |         for art_num in (last, first, last - 1): | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 resp, head = self.server.head(art_num) | 
					
						
							|  |  |  |             except nntplib.NNTPTemporaryError as e: | 
					
						
							|  |  |  |                 if not e.response.startswith("423 "): | 
					
						
							|  |  |  |                     raise | 
					
						
							|  |  |  |                 # "423 No such article" => choose another one | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             break | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.skipTest("could not find a suitable article number") | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         self.assertTrue(resp.startswith("221 "), resp) | 
					
						
							| 
									
										
										
										
											2010-11-18 15:11:43 +00:00
										 |  |  |         self.check_article_resp(resp, head, art_num) | 
					
						
							|  |  |  |         resp, body = self.server.body(art_num) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         self.assertTrue(resp.startswith("222 "), resp) | 
					
						
							| 
									
										
										
										
											2010-11-18 15:11:43 +00:00
										 |  |  |         self.check_article_resp(resp, body, art_num) | 
					
						
							|  |  |  |         resp, article = self.server.article(art_num) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         self.assertTrue(resp.startswith("220 "), resp) | 
					
						
							| 
									
										
										
										
											2010-11-18 15:11:43 +00:00
										 |  |  |         self.check_article_resp(resp, article, art_num) | 
					
						
							| 
									
										
										
										
											2012-06-17 21:27:18 +10:00
										 |  |  |         # Tolerate running the tests from behind a NNTP virus checker | 
					
						
							| 
									
										
										
										
											2020-08-11 15:26:59 +02:00
										 |  |  |         denylist = lambda line: line.startswith(b'X-Antivirus') | 
					
						
							| 
									
										
										
										
											2012-06-24 16:28:18 +02:00
										 |  |  |         filtered_head_lines = [line for line in head.lines | 
					
						
							| 
									
										
										
										
											2020-08-11 15:26:59 +02:00
										 |  |  |                                if not denylist(line)] | 
					
						
							| 
									
										
										
										
											2012-06-17 21:27:18 +10:00
										 |  |  |         filtered_lines = [line for line in article.lines | 
					
						
							| 
									
										
										
										
											2020-08-11 15:26:59 +02:00
										 |  |  |                           if not denylist(line)] | 
					
						
							| 
									
										
										
										
											2012-06-24 16:28:18 +02:00
										 |  |  |         self.assertEqual(filtered_lines, filtered_head_lines + [b''] + body.lines) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_capabilities(self): | 
					
						
							| 
									
										
										
										
											2010-11-09 18:54:37 +00:00
										 |  |  |         # The server under test implements NNTP version 2 and has a | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         # couple of well-known capabilities. Just sanity check that we | 
					
						
							|  |  |  |         # got them. | 
					
						
							|  |  |  |         def _check_caps(caps): | 
					
						
							|  |  |  |             caps_list = caps['LIST'] | 
					
						
							|  |  |  |             self.assertIsInstance(caps_list, (list, tuple)) | 
					
						
							|  |  |  |             self.assertIn('OVERVIEW.FMT', caps_list) | 
					
						
							|  |  |  |         self.assertGreaterEqual(self.server.nntp_version, 2) | 
					
						
							|  |  |  |         _check_caps(self.server.getcapabilities()) | 
					
						
							|  |  |  |         # This re-emits the command | 
					
						
							|  |  |  |         resp, caps = self.server.capabilities() | 
					
						
							|  |  |  |         _check_caps(caps) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-13 00:28:53 +00:00
										 |  |  |     def test_zlogin(self): | 
					
						
							|  |  |  |         # This test must be the penultimate because further commands will be | 
					
						
							|  |  |  |         # refused. | 
					
						
							|  |  |  |         baduser = "notarealuser" | 
					
						
							|  |  |  |         badpw = "notarealpassword" | 
					
						
							|  |  |  |         # Check that bogus credentials cause failure | 
					
						
							|  |  |  |         self.assertRaises(nntplib.NNTPError, self.server.login, | 
					
						
							|  |  |  |                           user=baduser, password=badpw, usenetrc=False) | 
					
						
							|  |  |  |         # FIXME: We should check that correct credentials succeed, but that | 
					
						
							|  |  |  |         # would require valid details for some server somewhere to be in the | 
					
						
							|  |  |  |         # test suite, I think. Gmane is anonymous, at least as used for the | 
					
						
							|  |  |  |         # other tests. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_zzquit(self): | 
					
						
							|  |  |  |         # This test must be called last, hence the name | 
					
						
							|  |  |  |         cls = type(self) | 
					
						
							| 
									
										
										
										
											2010-11-21 17:14:19 +00:00
										 |  |  |         try: | 
					
						
							|  |  |  |             self.server.quit() | 
					
						
							|  |  |  |         finally: | 
					
						
							|  |  |  |             cls.server = None | 
					
						
							| 
									
										
										
										
											2010-11-13 00:28:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-18 17:29:23 +00:00
										 |  |  |     @classmethod | 
					
						
							|  |  |  |     def wrap_methods(cls): | 
					
						
							|  |  |  |         # Wrap all methods in a transient_internet() exception catcher | 
					
						
							|  |  |  |         # XXX put a generic version in test.support? | 
					
						
							|  |  |  |         def wrap_meth(meth): | 
					
						
							|  |  |  |             @functools.wraps(meth) | 
					
						
							|  |  |  |             def wrapped(self): | 
					
						
							| 
									
										
										
										
											2020-04-29 10:36:20 +03:00
										 |  |  |                 with socket_helper.transient_internet(self.NNTP_HOST): | 
					
						
							| 
									
										
										
										
											2010-11-18 17:29:23 +00:00
										 |  |  |                     meth(self) | 
					
						
							|  |  |  |             return wrapped | 
					
						
							|  |  |  |         for name in dir(cls): | 
					
						
							|  |  |  |             if not name.startswith('test_'): | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             meth = getattr(cls, name) | 
					
						
							| 
									
										
										
										
											2011-10-28 14:45:05 +02:00
										 |  |  |             if not callable(meth): | 
					
						
							| 
									
										
										
										
											2010-11-18 17:29:23 +00:00
										 |  |  |                 continue | 
					
						
							|  |  |  |             # Need to use a closure so that meth remains bound to its current | 
					
						
							|  |  |  |             # value | 
					
						
							|  |  |  |             setattr(cls, name, wrap_meth(meth)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-12 02:39:15 +09:00
										 |  |  |     def test_timeout(self): | 
					
						
							|  |  |  |         with self.assertRaises(ValueError): | 
					
						
							|  |  |  |             self.NNTP_CLASS(self.NNTP_HOST, timeout=0, usenetrc=False) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-03 18:34:06 +00:00
										 |  |  |     def test_with_statement(self): | 
					
						
							|  |  |  |         def is_connected(): | 
					
						
							|  |  |  |             if not hasattr(server, 'file'): | 
					
						
							|  |  |  |                 return False | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 server.help() | 
					
						
							| 
									
										
										
										
											2012-12-18 23:10:48 +02:00
										 |  |  |             except (OSError, EOFError): | 
					
						
							| 
									
										
										
										
											2011-03-03 18:34:06 +00:00
										 |  |  |                 return False | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-06 17:54:06 -04:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2019-12-10 22:09:23 +01:00
										 |  |  |             server = self.NNTP_CLASS(self.NNTP_HOST, | 
					
						
							|  |  |  |                                      timeout=support.INTERNET_TIMEOUT, | 
					
						
							|  |  |  |                                      usenetrc=False) | 
					
						
							|  |  |  |             with server: | 
					
						
							| 
									
										
										
										
											2019-05-06 17:54:06 -04:00
										 |  |  |                 self.assertTrue(is_connected()) | 
					
						
							|  |  |  |                 self.assertTrue(server.help()) | 
					
						
							|  |  |  |             self.assertFalse(is_connected()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-10 22:09:23 +01:00
										 |  |  |             server = self.NNTP_CLASS(self.NNTP_HOST, | 
					
						
							|  |  |  |                                      timeout=support.INTERNET_TIMEOUT, | 
					
						
							|  |  |  |                                      usenetrc=False) | 
					
						
							|  |  |  |             with server: | 
					
						
							| 
									
										
										
										
											2019-05-06 17:54:06 -04:00
										 |  |  |                 server.quit() | 
					
						
							|  |  |  |             self.assertFalse(is_connected()) | 
					
						
							|  |  |  |         except SSLError as ssl_err: | 
					
						
							|  |  |  |             # matches "[SSL: DH_KEY_TOO_SMALL] dh key too small" | 
					
						
							|  |  |  |             if re.search(r'(?i)KEY.TOO.SMALL', ssl_err.reason): | 
					
						
							|  |  |  |                 raise unittest.SkipTest(f"Got {ssl_err} connecting " | 
					
						
							|  |  |  |                                         f"to {self.NNTP_HOST!r}") | 
					
						
							|  |  |  |             raise | 
					
						
							| 
									
										
										
										
											2011-03-03 18:34:06 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-18 17:29:23 +00:00
										 |  |  | NetworkedNNTPTestsMixin.wrap_methods() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-09 18:54:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-26 23:43:22 +09:00
										 |  |  | EOF_ERRORS = (EOFError,) | 
					
						
							| 
									
										
										
										
											2017-07-24 17:41:02 +02:00
										 |  |  | if ssl is not None: | 
					
						
							| 
									
										
										
										
											2017-07-26 23:43:22 +09:00
										 |  |  |     EOF_ERRORS += (ssl.SSLEOFError,) | 
					
						
							| 
									
										
										
										
											2017-07-24 17:41:02 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-09 18:54:37 +00:00
										 |  |  | class NetworkedNNTPTests(NetworkedNNTPTestsMixin, unittest.TestCase): | 
					
						
							|  |  |  |     # This server supports STARTTLS (gmane doesn't) | 
					
						
							|  |  |  |     NNTP_HOST = 'news.trigofacile.com' | 
					
						
							|  |  |  |     GROUP_NAME = 'fr.comp.lang.python' | 
					
						
							|  |  |  |     GROUP_PAT = 'fr.comp.lang.*' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-18 17:29:23 +00:00
										 |  |  |     NNTP_CLASS = NNTP | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-13 00:28:53 +00:00
										 |  |  |     @classmethod | 
					
						
							|  |  |  |     def setUpClass(cls): | 
					
						
							| 
									
										
										
										
											2010-11-09 18:54:37 +00:00
										 |  |  |         support.requires("network") | 
					
						
							| 
									
										
										
										
											2020-04-29 10:36:20 +03:00
										 |  |  |         with socket_helper.transient_internet(cls.NNTP_HOST): | 
					
						
							| 
									
										
										
										
											2017-04-27 17:30:13 +02:00
										 |  |  |             try: | 
					
						
							| 
									
										
										
										
											2019-12-10 22:09:23 +01:00
										 |  |  |                 cls.server = cls.NNTP_CLASS(cls.NNTP_HOST, | 
					
						
							|  |  |  |                                             timeout=support.INTERNET_TIMEOUT, | 
					
						
							| 
									
										
										
										
											2017-04-27 17:30:13 +02:00
										 |  |  |                                             usenetrc=False) | 
					
						
							| 
									
										
										
										
											2019-05-06 17:54:06 -04:00
										 |  |  |             except SSLError as ssl_err: | 
					
						
							|  |  |  |                 # matches "[SSL: DH_KEY_TOO_SMALL] dh key too small" | 
					
						
							|  |  |  |                 if re.search(r'(?i)KEY.TOO.SMALL', ssl_err.reason): | 
					
						
							|  |  |  |                     raise unittest.SkipTest(f"{cls} got {ssl_err} connecting " | 
					
						
							|  |  |  |                                             f"to {cls.NNTP_HOST!r}") | 
					
						
							|  |  |  |                 raise | 
					
						
							| 
									
										
										
										
											2017-07-24 17:41:02 +02:00
										 |  |  |             except EOF_ERRORS: | 
					
						
							| 
									
										
										
										
											2017-04-27 17:30:13 +02:00
										 |  |  |                 raise unittest.SkipTest(f"{cls} got EOF error on connecting " | 
					
						
							|  |  |  |                                         f"to {cls.NNTP_HOST!r}") | 
					
						
							| 
									
										
										
										
											2010-11-09 18:54:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-13 00:28:53 +00:00
										 |  |  |     @classmethod | 
					
						
							|  |  |  |     def tearDownClass(cls): | 
					
						
							|  |  |  |         if cls.server is not None: | 
					
						
							|  |  |  |             cls.server.quit() | 
					
						
							| 
									
										
										
										
											2010-11-09 18:54:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-03 21:31:38 +02:00
										 |  |  | @unittest.skipUnless(ssl, 'requires SSL support') | 
					
						
							|  |  |  | class NetworkedNNTP_SSLTests(NetworkedNNTPTests): | 
					
						
							| 
									
										
										
										
											2010-11-09 18:54:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-03 21:31:38 +02:00
										 |  |  |     # Technical limits for this public NNTP server (see http://www.aioe.org): | 
					
						
							|  |  |  |     # "Only two concurrent connections per IP address are allowed and | 
					
						
							|  |  |  |     # 400 connections per day are accepted from each IP address." | 
					
						
							| 
									
										
										
										
											2010-11-13 00:28:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-03 21:31:38 +02:00
										 |  |  |     NNTP_HOST = 'nntp.aioe.org' | 
					
						
							|  |  |  |     GROUP_NAME = 'comp.lang.python' | 
					
						
							|  |  |  |     GROUP_PAT = 'comp.lang.*' | 
					
						
							| 
									
										
										
										
											2010-11-13 00:28:53 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-03 21:31:38 +02:00
										 |  |  |     NNTP_CLASS = getattr(nntplib, 'NNTP_SSL', None) | 
					
						
							| 
									
										
										
										
											2010-11-09 18:54:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-03 21:31:38 +02:00
										 |  |  |     # Disabled as it produces too much data | 
					
						
							|  |  |  |     test_list = None | 
					
						
							| 
									
										
										
										
											2010-11-09 18:54:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-03 21:31:38 +02:00
										 |  |  |     # Disabled as the connection will already be encrypted. | 
					
						
							|  |  |  |     test_starttls = None | 
					
						
							| 
									
										
										
										
											2010-11-09 18:54:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Non-networked tests using a local server (or something mocking it). | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _NNTPServerIO(io.RawIOBase): | 
					
						
							|  |  |  |     """A raw IO object allowing NNTP commands to be received and processed
 | 
					
						
							|  |  |  |     by a handler.  The handler can push responses which can then be read | 
					
						
							|  |  |  |     from the IO object."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, handler): | 
					
						
							|  |  |  |         io.RawIOBase.__init__(self) | 
					
						
							|  |  |  |         # The channel from the client | 
					
						
							|  |  |  |         self.c2s = io.BytesIO() | 
					
						
							|  |  |  |         # The channel to the client | 
					
						
							|  |  |  |         self.s2c = io.BytesIO() | 
					
						
							|  |  |  |         self.handler = handler | 
					
						
							|  |  |  |         self.handler.start(self.c2s.readline, self.push_data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def readable(self): | 
					
						
							|  |  |  |         return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def writable(self): | 
					
						
							|  |  |  |         return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def push_data(self, data): | 
					
						
							|  |  |  |         """Push (buffer) some data to send to the client.""" | 
					
						
							|  |  |  |         pos = self.s2c.tell() | 
					
						
							|  |  |  |         self.s2c.seek(0, 2) | 
					
						
							|  |  |  |         self.s2c.write(data) | 
					
						
							|  |  |  |         self.s2c.seek(pos) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def write(self, b): | 
					
						
							|  |  |  |         """The client sends us some data""" | 
					
						
							|  |  |  |         pos = self.c2s.tell() | 
					
						
							|  |  |  |         self.c2s.write(b) | 
					
						
							|  |  |  |         self.c2s.seek(pos) | 
					
						
							|  |  |  |         self.handler.process_pending() | 
					
						
							|  |  |  |         return len(b) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def readinto(self, buf): | 
					
						
							|  |  |  |         """The client wants to read a response""" | 
					
						
							|  |  |  |         self.handler.process_pending() | 
					
						
							|  |  |  |         b = self.s2c.read(len(buf)) | 
					
						
							|  |  |  |         n = len(b) | 
					
						
							|  |  |  |         buf[:n] = b | 
					
						
							|  |  |  |         return n | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-21 09:40:26 +02:00
										 |  |  | def make_mock_file(handler): | 
					
						
							|  |  |  |     sio = _NNTPServerIO(handler) | 
					
						
							|  |  |  |     # Using BufferedRWPair instead of BufferedRandom ensures the file | 
					
						
							|  |  |  |     # isn't seekable. | 
					
						
							|  |  |  |     file = io.BufferedRWPair(sio, sio) | 
					
						
							|  |  |  |     return (sio, file) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-16 19:31:54 +09:00
										 |  |  | class NNTPServer(nntplib.NNTP): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, f, host, readermode=None): | 
					
						
							|  |  |  |         self.file = f | 
					
						
							|  |  |  |         self.host = host | 
					
						
							|  |  |  |         self._base_init(readermode) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _close(self): | 
					
						
							|  |  |  |         self.file.close() | 
					
						
							|  |  |  |         del self.file | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | class MockedNNTPTestsMixin: | 
					
						
							|  |  |  |     # Override in derived classes | 
					
						
							|  |  |  |     handler_class = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def setUp(self): | 
					
						
							|  |  |  |         super().setUp() | 
					
						
							|  |  |  |         self.make_server() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def tearDown(self): | 
					
						
							|  |  |  |         super().tearDown() | 
					
						
							|  |  |  |         del self.server | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def make_server(self, *args, **kwargs): | 
					
						
							|  |  |  |         self.handler = self.handler_class() | 
					
						
							| 
									
										
										
										
											2015-03-21 09:40:26 +02:00
										 |  |  |         self.sio, file = make_mock_file(self.handler) | 
					
						
							| 
									
										
										
										
											2020-05-16 19:31:54 +09:00
										 |  |  |         self.server = NNTPServer(file, 'test.server', *args, **kwargs) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         return self.server | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-14 23:29:34 +01:00
										 |  |  | class MockedNNTPWithReaderModeMixin(MockedNNTPTestsMixin): | 
					
						
							|  |  |  |     def setUp(self): | 
					
						
							|  |  |  |         super().setUp() | 
					
						
							|  |  |  |         self.make_server(readermode=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | class NNTPv1Handler: | 
					
						
							|  |  |  |     """A handler for RFC 977""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     welcome = "200 NNTP mock server" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def start(self, readline, push_data): | 
					
						
							|  |  |  |         self.in_body = False | 
					
						
							|  |  |  |         self.allow_posting = True | 
					
						
							|  |  |  |         self._readline = readline | 
					
						
							|  |  |  |         self._push_data = push_data | 
					
						
							| 
									
										
										
										
											2012-02-12 19:14:17 +01:00
										 |  |  |         self._logged_in = False | 
					
						
							|  |  |  |         self._user_sent = False | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         # Our welcome | 
					
						
							|  |  |  |         self.handle_welcome() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _decode(self, data): | 
					
						
							|  |  |  |         return str(data, "utf-8", "surrogateescape") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def process_pending(self): | 
					
						
							|  |  |  |         if self.in_body: | 
					
						
							|  |  |  |             while True: | 
					
						
							|  |  |  |                 line = self._readline() | 
					
						
							|  |  |  |                 if not line: | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 self.body.append(line) | 
					
						
							|  |  |  |                 if line == b".\r\n": | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 meth, tokens = self.body_callback | 
					
						
							|  |  |  |                 meth(*tokens, body=self.body) | 
					
						
							|  |  |  |             finally: | 
					
						
							|  |  |  |                 self.body_callback = None | 
					
						
							|  |  |  |                 self.body = None | 
					
						
							|  |  |  |                 self.in_body = False | 
					
						
							|  |  |  |         while True: | 
					
						
							|  |  |  |             line = self._decode(self._readline()) | 
					
						
							|  |  |  |             if not line: | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             if not line.endswith("\r\n"): | 
					
						
							|  |  |  |                 raise ValueError("line doesn't end with \\r\\n: {!r}".format(line)) | 
					
						
							|  |  |  |             line = line[:-2] | 
					
						
							|  |  |  |             cmd, *tokens = line.split() | 
					
						
							|  |  |  |             #meth = getattr(self.handler, "handle_" + cmd.upper(), None) | 
					
						
							|  |  |  |             meth = getattr(self, "handle_" + cmd.upper(), None) | 
					
						
							|  |  |  |             if meth is None: | 
					
						
							|  |  |  |                 self.handle_unknown() | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     meth(*tokens) | 
					
						
							|  |  |  |                 except Exception as e: | 
					
						
							|  |  |  |                     raise ValueError("command failed: {!r}".format(line)) from e | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     if self.in_body: | 
					
						
							|  |  |  |                         self.body_callback = meth, tokens | 
					
						
							|  |  |  |                         self.body = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def expect_body(self): | 
					
						
							|  |  |  |         """Flag that the client is expected to post a request body""" | 
					
						
							|  |  |  |         self.in_body = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def push_data(self, data): | 
					
						
							|  |  |  |         """Push some binary data""" | 
					
						
							|  |  |  |         self._push_data(data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def push_lit(self, lit): | 
					
						
							|  |  |  |         """Push a string literal""" | 
					
						
							|  |  |  |         lit = textwrap.dedent(lit) | 
					
						
							|  |  |  |         lit = "\r\n".join(lit.splitlines()) + "\r\n" | 
					
						
							|  |  |  |         lit = lit.encode('utf-8') | 
					
						
							|  |  |  |         self.push_data(lit) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_unknown(self): | 
					
						
							|  |  |  |         self.push_lit("500 What?") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_welcome(self): | 
					
						
							|  |  |  |         self.push_lit(self.welcome) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_QUIT(self): | 
					
						
							|  |  |  |         self.push_lit("205 Bye!") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_DATE(self): | 
					
						
							|  |  |  |         self.push_lit("111 20100914001155") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_GROUP(self, group): | 
					
						
							|  |  |  |         if group == "fr.comp.lang.python": | 
					
						
							|  |  |  |             self.push_lit("211 486 761 1265 fr.comp.lang.python") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.push_lit("411 No such group {}".format(group)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_HELP(self): | 
					
						
							|  |  |  |         self.push_lit("""\
 | 
					
						
							|  |  |  |             100 Legal commands | 
					
						
							|  |  |  |               authinfo user Name|pass Password|generic <prog> <args> | 
					
						
							|  |  |  |               date | 
					
						
							|  |  |  |               help | 
					
						
							|  |  |  |             Report problems to <root@example.org> | 
					
						
							|  |  |  |             .""")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_STAT(self, message_spec=None): | 
					
						
							|  |  |  |         if message_spec is None: | 
					
						
							|  |  |  |             self.push_lit("412 No newsgroup selected") | 
					
						
							|  |  |  |         elif message_spec == "3000234": | 
					
						
							|  |  |  |             self.push_lit("223 3000234 <45223423@example.com>") | 
					
						
							|  |  |  |         elif message_spec == "<45223423@example.com>": | 
					
						
							|  |  |  |             self.push_lit("223 0 <45223423@example.com>") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.push_lit("430 No Such Article Found") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_NEXT(self): | 
					
						
							|  |  |  |         self.push_lit("223 3000237 <668929@example.org> retrieved") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_LAST(self): | 
					
						
							|  |  |  |         self.push_lit("223 3000234 <45223423@example.com> retrieved") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_LIST(self, action=None, param=None): | 
					
						
							|  |  |  |         if action is None: | 
					
						
							|  |  |  |             self.push_lit("""\
 | 
					
						
							|  |  |  |                 215 Newsgroups in form "group high low flags". | 
					
						
							|  |  |  |                 comp.lang.python 0000052340 0000002828 y | 
					
						
							|  |  |  |                 comp.lang.python.announce 0000001153 0000000993 m | 
					
						
							|  |  |  |                 free.it.comp.lang.python 0000000002 0000000002 y | 
					
						
							|  |  |  |                 fr.comp.lang.python 0000001254 0000000760 y | 
					
						
							|  |  |  |                 free.it.comp.lang.python.learner 0000000000 0000000001 y | 
					
						
							|  |  |  |                 tw.bbs.comp.lang.python 0000000304 0000000304 y | 
					
						
							|  |  |  |                 .""")
 | 
					
						
							| 
									
										
										
										
											2010-11-04 21:36:15 +00:00
										 |  |  |         elif action == "ACTIVE": | 
					
						
							|  |  |  |             if param == "*distutils*": | 
					
						
							|  |  |  |                 self.push_lit("""\
 | 
					
						
							|  |  |  |                     215 Newsgroups in form "group high low flags" | 
					
						
							|  |  |  |                     gmane.comp.python.distutils.devel 0000014104 0000000001 m | 
					
						
							|  |  |  |                     gmane.comp.python.distutils.cvs 0000000000 0000000001 m | 
					
						
							|  |  |  |                     .""")
 | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.push_lit("""\
 | 
					
						
							|  |  |  |                     215 Newsgroups in form "group high low flags" | 
					
						
							|  |  |  |                     .""")
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         elif action == "OVERVIEW.FMT": | 
					
						
							|  |  |  |             self.push_lit("""\
 | 
					
						
							|  |  |  |                 215 Order of fields in overview database. | 
					
						
							|  |  |  |                 Subject: | 
					
						
							|  |  |  |                 From: | 
					
						
							|  |  |  |                 Date: | 
					
						
							|  |  |  |                 Message-ID: | 
					
						
							|  |  |  |                 References: | 
					
						
							|  |  |  |                 Bytes: | 
					
						
							|  |  |  |                 Lines: | 
					
						
							|  |  |  |                 Xref:full | 
					
						
							|  |  |  |                 .""")
 | 
					
						
							|  |  |  |         elif action == "NEWSGROUPS": | 
					
						
							|  |  |  |             assert param is not None | 
					
						
							|  |  |  |             if param == "comp.lang.python": | 
					
						
							|  |  |  |                 self.push_lit("""\
 | 
					
						
							|  |  |  |                     215 Descriptions in form "group description". | 
					
						
							|  |  |  |                     comp.lang.python\tThe Python computer language. | 
					
						
							|  |  |  |                     .""")
 | 
					
						
							|  |  |  |             elif param == "comp.lang.python*": | 
					
						
							|  |  |  |                 self.push_lit("""\
 | 
					
						
							|  |  |  |                     215 Descriptions in form "group description". | 
					
						
							|  |  |  |                     comp.lang.python.announce\tAnnouncements about the Python language. (Moderated) | 
					
						
							|  |  |  |                     comp.lang.python\tThe Python computer language. | 
					
						
							|  |  |  |                     .""")
 | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.push_lit("""\
 | 
					
						
							|  |  |  |                     215 Descriptions in form "group description". | 
					
						
							|  |  |  |                     .""")
 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.push_lit('501 Unknown LIST keyword') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_NEWNEWS(self, group, date_str, time_str): | 
					
						
							|  |  |  |         # We hard code different return messages depending on passed | 
					
						
							|  |  |  |         # argument and date syntax. | 
					
						
							|  |  |  |         if (group == "comp.lang.python" and date_str == "20100913" | 
					
						
							|  |  |  |             and time_str == "082004"): | 
					
						
							|  |  |  |             # Date was passed in RFC 3977 format (NNTP "v2") | 
					
						
							|  |  |  |             self.push_lit("""\
 | 
					
						
							|  |  |  |                 230 list of newsarticles (NNTP v2) created after Mon Sep 13 08:20:04 2010 follows | 
					
						
							|  |  |  |                 <a4929a40-6328-491a-aaaf-cb79ed7309a2@q2g2000vbk.googlegroups.com> | 
					
						
							|  |  |  |                 <f30c0419-f549-4218-848f-d7d0131da931@y3g2000vbm.googlegroups.com> | 
					
						
							|  |  |  |                 .""")
 | 
					
						
							|  |  |  |         elif (group == "comp.lang.python" and date_str == "100913" | 
					
						
							|  |  |  |             and time_str == "082004"): | 
					
						
							|  |  |  |             # Date was passed in RFC 977 format (NNTP "v1") | 
					
						
							|  |  |  |             self.push_lit("""\
 | 
					
						
							|  |  |  |                 230 list of newsarticles (NNTP v1) created after Mon Sep 13 08:20:04 2010 follows | 
					
						
							|  |  |  |                 <a4929a40-6328-491a-aaaf-cb79ed7309a2@q2g2000vbk.googlegroups.com> | 
					
						
							|  |  |  |                 <f30c0419-f549-4218-848f-d7d0131da931@y3g2000vbm.googlegroups.com> | 
					
						
							|  |  |  |                 .""")
 | 
					
						
							| 
									
										
										
										
											2013-10-27 07:29:47 +01:00
										 |  |  |         elif (group == 'comp.lang.python' and | 
					
						
							|  |  |  |               date_str in ('20100101', '100101') and | 
					
						
							|  |  |  |               time_str == '090000'): | 
					
						
							|  |  |  |             self.push_lit('too long line' * 3000 + | 
					
						
							|  |  |  |                           '\n.') | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         else: | 
					
						
							|  |  |  |             self.push_lit("""\
 | 
					
						
							|  |  |  |                 230 An empty list of newsarticles follows | 
					
						
							|  |  |  |                 .""")
 | 
					
						
							|  |  |  |         # (Note for experiments: many servers disable NEWNEWS. | 
					
						
							|  |  |  |         #  As of this writing, sicinfo3.epfl.ch doesn't.) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_XOVER(self, message_spec): | 
					
						
							|  |  |  |         if message_spec == "57-59": | 
					
						
							|  |  |  |             self.push_lit( | 
					
						
							|  |  |  |                 "224 Overview information for 57-58 follows\n" | 
					
						
							|  |  |  |                 "57\tRe: ANN: New Plone book with strong Python (and Zope) themes throughout" | 
					
						
							|  |  |  |                     "\tDoug Hellmann <doug.hellmann-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>" | 
					
						
							|  |  |  |                     "\tSat, 19 Jun 2010 18:04:08 -0400" | 
					
						
							|  |  |  |                     "\t<4FD05F05-F98B-44DC-8111-C6009C925F0C@gmail.com>" | 
					
						
							|  |  |  |                     "\t<hvalf7$ort$1@dough.gmane.org>\t7103\t16" | 
					
						
							| 
									
										
										
										
											2020-01-09 00:29:34 +09:00
										 |  |  |                     "\tXref: news.gmane.io gmane.comp.python.authors:57" | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |                     "\n" | 
					
						
							|  |  |  |                 "58\tLooking for a few good bloggers" | 
					
						
							|  |  |  |                     "\tDoug Hellmann <doug.hellmann-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>" | 
					
						
							|  |  |  |                     "\tThu, 22 Jul 2010 09:14:14 -0400" | 
					
						
							|  |  |  |                     "\t<A29863FA-F388-40C3-AA25-0FD06B09B5BF@gmail.com>" | 
					
						
							|  |  |  |                     "\t\t6683\t16" | 
					
						
							| 
									
										
										
										
											2010-11-03 18:18:43 +00:00
										 |  |  |                     "\t" | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |                     "\n" | 
					
						
							| 
									
										
										
										
											2016-04-15 02:14:19 +00:00
										 |  |  |                 # A UTF-8 overview line from fr.comp.lang.python | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |                 "59\tRe: Message d'erreur incompréhensible (par moi)" | 
					
						
							|  |  |  |                     "\tEric Brunel <eric.brunel@pragmadev.nospam.com>" | 
					
						
							|  |  |  |                     "\tWed, 15 Sep 2010 18:09:15 +0200" | 
					
						
							|  |  |  |                     "\t<eric.brunel-2B8B56.18091515092010@news.wanadoo.fr>" | 
					
						
							|  |  |  |                     "\t<4c90ec87$0$32425$ba4acef3@reader.news.orange.fr>\t1641\t27" | 
					
						
							|  |  |  |                     "\tXref: saria.nerim.net fr.comp.lang.python:1265" | 
					
						
							|  |  |  |                     "\n" | 
					
						
							|  |  |  |                 ".\n") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.push_lit("""\
 | 
					
						
							|  |  |  |                 224 No articles | 
					
						
							|  |  |  |                 .""")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_POST(self, *, body=None): | 
					
						
							|  |  |  |         if body is None: | 
					
						
							|  |  |  |             if self.allow_posting: | 
					
						
							|  |  |  |                 self.push_lit("340 Input article; end with <CR-LF>.<CR-LF>") | 
					
						
							|  |  |  |                 self.expect_body() | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.push_lit("440 Posting not permitted") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             assert self.allow_posting | 
					
						
							|  |  |  |             self.push_lit("240 Article received OK") | 
					
						
							|  |  |  |             self.posted_body = body | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_IHAVE(self, message_id, *, body=None): | 
					
						
							|  |  |  |         if body is None: | 
					
						
							|  |  |  |             if (self.allow_posting and | 
					
						
							|  |  |  |                 message_id == "<i.am.an.article.you.will.want@example.com>"): | 
					
						
							|  |  |  |                 self.push_lit("335 Send it; end with <CR-LF>.<CR-LF>") | 
					
						
							|  |  |  |                 self.expect_body() | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.push_lit("435 Article not wanted") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             assert self.allow_posting | 
					
						
							|  |  |  |             self.push_lit("235 Article transferred OK") | 
					
						
							|  |  |  |             self.posted_body = body | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sample_head = """\
 | 
					
						
							|  |  |  |         From: "Demo User" <nobody@example.net> | 
					
						
							|  |  |  |         Subject: I am just a test article | 
					
						
							|  |  |  |         Content-Type: text/plain; charset=UTF-8; format=flowed | 
					
						
							|  |  |  |         Message-ID: <i.am.an.article.you.will.want@example.com>"""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sample_body = """\
 | 
					
						
							|  |  |  |         This is just a test article. | 
					
						
							|  |  |  |         ..Here is a dot-starting line. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         -- Signed by Andr\xe9."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sample_article = sample_head + "\n\n" + sample_body | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_ARTICLE(self, message_spec=None): | 
					
						
							|  |  |  |         if message_spec is None: | 
					
						
							|  |  |  |             self.push_lit("220 3000237 <45223423@example.com>") | 
					
						
							|  |  |  |         elif message_spec == "<45223423@example.com>": | 
					
						
							|  |  |  |             self.push_lit("220 0 <45223423@example.com>") | 
					
						
							|  |  |  |         elif message_spec == "3000234": | 
					
						
							|  |  |  |             self.push_lit("220 3000234 <45223423@example.com>") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.push_lit("430 No Such Article Found") | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         self.push_lit(self.sample_article) | 
					
						
							|  |  |  |         self.push_lit(".") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_HEAD(self, message_spec=None): | 
					
						
							|  |  |  |         if message_spec is None: | 
					
						
							|  |  |  |             self.push_lit("221 3000237 <45223423@example.com>") | 
					
						
							|  |  |  |         elif message_spec == "<45223423@example.com>": | 
					
						
							|  |  |  |             self.push_lit("221 0 <45223423@example.com>") | 
					
						
							|  |  |  |         elif message_spec == "3000234": | 
					
						
							|  |  |  |             self.push_lit("221 3000234 <45223423@example.com>") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.push_lit("430 No Such Article Found") | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         self.push_lit(self.sample_head) | 
					
						
							|  |  |  |         self.push_lit(".") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_BODY(self, message_spec=None): | 
					
						
							|  |  |  |         if message_spec is None: | 
					
						
							|  |  |  |             self.push_lit("222 3000237 <45223423@example.com>") | 
					
						
							|  |  |  |         elif message_spec == "<45223423@example.com>": | 
					
						
							|  |  |  |             self.push_lit("222 0 <45223423@example.com>") | 
					
						
							|  |  |  |         elif message_spec == "3000234": | 
					
						
							|  |  |  |             self.push_lit("222 3000234 <45223423@example.com>") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.push_lit("430 No Such Article Found") | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         self.push_lit(self.sample_body) | 
					
						
							|  |  |  |         self.push_lit(".") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-12 19:14:17 +01:00
										 |  |  |     def handle_AUTHINFO(self, cred_type, data): | 
					
						
							|  |  |  |         if self._logged_in: | 
					
						
							|  |  |  |             self.push_lit('502 Already Logged In') | 
					
						
							|  |  |  |         elif cred_type == 'user': | 
					
						
							|  |  |  |             if self._user_sent: | 
					
						
							|  |  |  |                 self.push_lit('482 User Credential Already Sent') | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.push_lit('381 Password Required') | 
					
						
							|  |  |  |                 self._user_sent = True | 
					
						
							|  |  |  |         elif cred_type == 'pass': | 
					
						
							|  |  |  |             self.push_lit('281 Login Successful') | 
					
						
							|  |  |  |             self._logged_in = True | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise Exception('Unknown cred type {}'.format(cred_type)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | class NNTPv2Handler(NNTPv1Handler): | 
					
						
							|  |  |  |     """A handler for RFC 3977 (NNTP "v2")""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_CAPABILITIES(self): | 
					
						
							| 
									
										
										
										
											2012-02-12 19:14:17 +01:00
										 |  |  |         fmt = """\
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |             101 Capability list: | 
					
						
							| 
									
										
										
										
											2010-11-02 22:31:52 +00:00
										 |  |  |             VERSION 2 3 | 
					
						
							| 
									
										
										
										
											2012-02-12 19:14:17 +01:00
										 |  |  |             IMPLEMENTATION INN 2.5.1{} | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |             HDR | 
					
						
							|  |  |  |             LIST ACTIVE ACTIVE.TIMES DISTRIB.PATS HEADERS NEWSGROUPS OVERVIEW.FMT | 
					
						
							|  |  |  |             OVER | 
					
						
							|  |  |  |             POST | 
					
						
							|  |  |  |             READER | 
					
						
							| 
									
										
										
										
											2012-02-12 19:14:17 +01:00
										 |  |  |             ."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not self._logged_in: | 
					
						
							|  |  |  |             self.push_lit(fmt.format('\n            AUTHINFO USER')) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.push_lit(fmt.format('')) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-14 23:29:34 +01:00
										 |  |  |     def handle_MODE(self, _): | 
					
						
							|  |  |  |         raise Exception('MODE READER sent despite READER has been advertised') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |     def handle_OVER(self, message_spec=None): | 
					
						
							|  |  |  |         return self.handle_XOVER(message_spec) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-12 19:14:17 +01:00
										 |  |  | class CapsAfterLoginNNTPv2Handler(NNTPv2Handler): | 
					
						
							|  |  |  |     """A handler that allows CAPABILITIES only after login""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_CAPABILITIES(self): | 
					
						
							|  |  |  |         if not self._logged_in: | 
					
						
							|  |  |  |             self.push_lit('480 You must log in.') | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             super().handle_CAPABILITIES() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-14 23:29:34 +01:00
										 |  |  | class ModeSwitchingNNTPv2Handler(NNTPv2Handler): | 
					
						
							|  |  |  |     """A server that starts in transit mode""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self): | 
					
						
							|  |  |  |         self._switched = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_CAPABILITIES(self): | 
					
						
							|  |  |  |         fmt = """\
 | 
					
						
							|  |  |  |             101 Capability list: | 
					
						
							|  |  |  |             VERSION 2 3 | 
					
						
							|  |  |  |             IMPLEMENTATION INN 2.5.1 | 
					
						
							|  |  |  |             HDR | 
					
						
							|  |  |  |             LIST ACTIVE ACTIVE.TIMES DISTRIB.PATS HEADERS NEWSGROUPS OVERVIEW.FMT | 
					
						
							|  |  |  |             OVER | 
					
						
							|  |  |  |             POST | 
					
						
							|  |  |  |             {}READER | 
					
						
							|  |  |  |             ."""
 | 
					
						
							|  |  |  |         if self._switched: | 
					
						
							|  |  |  |             self.push_lit(fmt.format('')) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.push_lit(fmt.format('MODE-')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def handle_MODE(self, what): | 
					
						
							|  |  |  |         assert not self._switched and what == 'reader' | 
					
						
							|  |  |  |         self._switched = True | 
					
						
							|  |  |  |         self.push_lit('200 Posting allowed') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | class NNTPv1v2TestsMixin: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def setUp(self): | 
					
						
							|  |  |  |         super().setUp() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_welcome(self): | 
					
						
							|  |  |  |         self.assertEqual(self.server.welcome, self.handler.welcome) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-12 19:14:17 +01:00
										 |  |  |     def test_authinfo(self): | 
					
						
							|  |  |  |         if self.nntp_version == 2: | 
					
						
							|  |  |  |             self.assertIn('AUTHINFO', self.server._caps) | 
					
						
							|  |  |  |         self.server.login('testuser', 'testpw') | 
					
						
							|  |  |  |         # if AUTHINFO is gone from _caps we also know that getcapabilities() | 
					
						
							|  |  |  |         # has been called after login as it should | 
					
						
							|  |  |  |         self.assertNotIn('AUTHINFO', self.server._caps) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |     def test_date(self): | 
					
						
							|  |  |  |         resp, date = self.server.date() | 
					
						
							|  |  |  |         self.assertEqual(resp, "111 20100914001155") | 
					
						
							|  |  |  |         self.assertEqual(date, datetime.datetime(2010, 9, 14, 0, 11, 55)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_quit(self): | 
					
						
							|  |  |  |         self.assertFalse(self.sio.closed) | 
					
						
							|  |  |  |         resp = self.server.quit() | 
					
						
							|  |  |  |         self.assertEqual(resp, "205 Bye!") | 
					
						
							|  |  |  |         self.assertTrue(self.sio.closed) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_help(self): | 
					
						
							|  |  |  |         resp, help = self.server.help() | 
					
						
							|  |  |  |         self.assertEqual(resp, "100 Legal commands") | 
					
						
							|  |  |  |         self.assertEqual(help, [ | 
					
						
							|  |  |  |             '  authinfo user Name|pass Password|generic <prog> <args>', | 
					
						
							|  |  |  |             '  date', | 
					
						
							|  |  |  |             '  help', | 
					
						
							|  |  |  |             'Report problems to <root@example.org>', | 
					
						
							|  |  |  |         ]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_list(self): | 
					
						
							|  |  |  |         resp, groups = self.server.list() | 
					
						
							|  |  |  |         self.assertEqual(len(groups), 6) | 
					
						
							|  |  |  |         g = groups[1] | 
					
						
							|  |  |  |         self.assertEqual(g, | 
					
						
							|  |  |  |             GroupInfo("comp.lang.python.announce", "0000001153", | 
					
						
							|  |  |  |                       "0000000993", "m")) | 
					
						
							| 
									
										
										
										
											2010-11-04 21:36:15 +00:00
										 |  |  |         resp, groups = self.server.list("*distutils*") | 
					
						
							|  |  |  |         self.assertEqual(len(groups), 2) | 
					
						
							|  |  |  |         g = groups[0] | 
					
						
							|  |  |  |         self.assertEqual(g, | 
					
						
							|  |  |  |             GroupInfo("gmane.comp.python.distutils.devel", "0000014104", | 
					
						
							|  |  |  |                       "0000000001", "m")) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_stat(self): | 
					
						
							|  |  |  |         resp, art_num, message_id = self.server.stat(3000234) | 
					
						
							|  |  |  |         self.assertEqual(resp, "223 3000234 <45223423@example.com>") | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000234) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         resp, art_num, message_id = self.server.stat("<45223423@example.com>") | 
					
						
							|  |  |  |         self.assertEqual(resp, "223 0 <45223423@example.com>") | 
					
						
							|  |  |  |         self.assertEqual(art_num, 0) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         with self.assertRaises(nntplib.NNTPTemporaryError) as cm: | 
					
						
							|  |  |  |             self.server.stat("<non.existent.id>") | 
					
						
							|  |  |  |         self.assertEqual(cm.exception.response, "430 No Such Article Found") | 
					
						
							|  |  |  |         with self.assertRaises(nntplib.NNTPTemporaryError) as cm: | 
					
						
							|  |  |  |             self.server.stat() | 
					
						
							|  |  |  |         self.assertEqual(cm.exception.response, "412 No newsgroup selected") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_next(self): | 
					
						
							|  |  |  |         resp, art_num, message_id = self.server.next() | 
					
						
							|  |  |  |         self.assertEqual(resp, "223 3000237 <668929@example.org> retrieved") | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000237) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<668929@example.org>") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_last(self): | 
					
						
							|  |  |  |         resp, art_num, message_id = self.server.last() | 
					
						
							|  |  |  |         self.assertEqual(resp, "223 3000234 <45223423@example.com> retrieved") | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000234) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_description(self): | 
					
						
							|  |  |  |         desc = self.server.description("comp.lang.python") | 
					
						
							|  |  |  |         self.assertEqual(desc, "The Python computer language.") | 
					
						
							|  |  |  |         desc = self.server.description("comp.lang.pythonx") | 
					
						
							|  |  |  |         self.assertEqual(desc, "") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_descriptions(self): | 
					
						
							|  |  |  |         resp, groups = self.server.descriptions("comp.lang.python") | 
					
						
							|  |  |  |         self.assertEqual(resp, '215 Descriptions in form "group description".') | 
					
						
							|  |  |  |         self.assertEqual(groups, { | 
					
						
							|  |  |  |             "comp.lang.python": "The Python computer language.", | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         resp, groups = self.server.descriptions("comp.lang.python*") | 
					
						
							|  |  |  |         self.assertEqual(groups, { | 
					
						
							|  |  |  |             "comp.lang.python": "The Python computer language.", | 
					
						
							|  |  |  |             "comp.lang.python.announce": "Announcements about the Python language. (Moderated)", | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         resp, groups = self.server.descriptions("comp.lang.pythonx") | 
					
						
							|  |  |  |         self.assertEqual(groups, {}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_group(self): | 
					
						
							|  |  |  |         resp, count, first, last, group = self.server.group("fr.comp.lang.python") | 
					
						
							|  |  |  |         self.assertTrue(resp.startswith("211 "), resp) | 
					
						
							|  |  |  |         self.assertEqual(first, 761) | 
					
						
							|  |  |  |         self.assertEqual(last, 1265) | 
					
						
							|  |  |  |         self.assertEqual(count, 486) | 
					
						
							|  |  |  |         self.assertEqual(group, "fr.comp.lang.python") | 
					
						
							|  |  |  |         with self.assertRaises(nntplib.NNTPTemporaryError) as cm: | 
					
						
							|  |  |  |             self.server.group("comp.lang.python.devel") | 
					
						
							|  |  |  |         exc = cm.exception | 
					
						
							|  |  |  |         self.assertTrue(exc.response.startswith("411 No such group"), | 
					
						
							|  |  |  |                         exc.response) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_newnews(self): | 
					
						
							|  |  |  |         # NEWNEWS comp.lang.python [20]100913 082004 | 
					
						
							|  |  |  |         dt = datetime.datetime(2010, 9, 13, 8, 20, 4) | 
					
						
							|  |  |  |         resp, ids = self.server.newnews("comp.lang.python", dt) | 
					
						
							|  |  |  |         expected = ( | 
					
						
							|  |  |  |             "230 list of newsarticles (NNTP v{0}) " | 
					
						
							|  |  |  |             "created after Mon Sep 13 08:20:04 2010 follows" | 
					
						
							|  |  |  |             ).format(self.nntp_version) | 
					
						
							|  |  |  |         self.assertEqual(resp, expected) | 
					
						
							|  |  |  |         self.assertEqual(ids, [ | 
					
						
							|  |  |  |             "<a4929a40-6328-491a-aaaf-cb79ed7309a2@q2g2000vbk.googlegroups.com>", | 
					
						
							|  |  |  |             "<f30c0419-f549-4218-848f-d7d0131da931@y3g2000vbm.googlegroups.com>", | 
					
						
							|  |  |  |             ]) | 
					
						
							|  |  |  |         # NEWNEWS fr.comp.lang.python [20]100913 082004 | 
					
						
							|  |  |  |         dt = datetime.datetime(2010, 9, 13, 8, 20, 4) | 
					
						
							|  |  |  |         resp, ids = self.server.newnews("fr.comp.lang.python", dt) | 
					
						
							|  |  |  |         self.assertEqual(resp, "230 An empty list of newsarticles follows") | 
					
						
							|  |  |  |         self.assertEqual(ids, []) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _check_article_body(self, lines): | 
					
						
							|  |  |  |         self.assertEqual(len(lines), 4) | 
					
						
							| 
									
										
										
										
											2011-02-25 15:42:01 +00:00
										 |  |  |         self.assertEqual(lines[-1].decode('utf-8'), "-- Signed by André.") | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         self.assertEqual(lines[-2], b"") | 
					
						
							|  |  |  |         self.assertEqual(lines[-3], b".Here is a dot-starting line.") | 
					
						
							|  |  |  |         self.assertEqual(lines[-4], b"This is just a test article.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _check_article_head(self, lines): | 
					
						
							|  |  |  |         self.assertEqual(len(lines), 4) | 
					
						
							|  |  |  |         self.assertEqual(lines[0], b'From: "Demo User" <nobody@example.net>') | 
					
						
							|  |  |  |         self.assertEqual(lines[3], b"Message-ID: <i.am.an.article.you.will.want@example.com>") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _check_article_data(self, lines): | 
					
						
							|  |  |  |         self.assertEqual(len(lines), 9) | 
					
						
							|  |  |  |         self._check_article_head(lines[:4]) | 
					
						
							|  |  |  |         self._check_article_body(lines[-4:]) | 
					
						
							|  |  |  |         self.assertEqual(lines[4], b"") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_article(self): | 
					
						
							|  |  |  |         # ARTICLE | 
					
						
							|  |  |  |         resp, info = self.server.article() | 
					
						
							|  |  |  |         self.assertEqual(resp, "220 3000237 <45223423@example.com>") | 
					
						
							|  |  |  |         art_num, message_id, lines = info | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000237) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         self._check_article_data(lines) | 
					
						
							|  |  |  |         # ARTICLE num | 
					
						
							|  |  |  |         resp, info = self.server.article(3000234) | 
					
						
							|  |  |  |         self.assertEqual(resp, "220 3000234 <45223423@example.com>") | 
					
						
							|  |  |  |         art_num, message_id, lines = info | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000234) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         self._check_article_data(lines) | 
					
						
							|  |  |  |         # ARTICLE id | 
					
						
							|  |  |  |         resp, info = self.server.article("<45223423@example.com>") | 
					
						
							|  |  |  |         self.assertEqual(resp, "220 0 <45223423@example.com>") | 
					
						
							|  |  |  |         art_num, message_id, lines = info | 
					
						
							|  |  |  |         self.assertEqual(art_num, 0) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         self._check_article_data(lines) | 
					
						
							|  |  |  |         # Non-existent id | 
					
						
							|  |  |  |         with self.assertRaises(nntplib.NNTPTemporaryError) as cm: | 
					
						
							|  |  |  |             self.server.article("<non-existent@example.com>") | 
					
						
							|  |  |  |         self.assertEqual(cm.exception.response, "430 No Such Article Found") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_article_file(self): | 
					
						
							|  |  |  |         # With a "file" argument | 
					
						
							|  |  |  |         f = io.BytesIO() | 
					
						
							|  |  |  |         resp, info = self.server.article(file=f) | 
					
						
							|  |  |  |         self.assertEqual(resp, "220 3000237 <45223423@example.com>") | 
					
						
							|  |  |  |         art_num, message_id, lines = info | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000237) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         self.assertEqual(lines, []) | 
					
						
							|  |  |  |         data = f.getvalue() | 
					
						
							|  |  |  |         self.assertTrue(data.startswith( | 
					
						
							|  |  |  |             b'From: "Demo User" <nobody@example.net>\r\n' | 
					
						
							|  |  |  |             b'Subject: I am just a test article\r\n' | 
					
						
							|  |  |  |             ), ascii(data)) | 
					
						
							|  |  |  |         self.assertTrue(data.endswith( | 
					
						
							|  |  |  |             b'This is just a test article.\r\n' | 
					
						
							|  |  |  |             b'.Here is a dot-starting line.\r\n' | 
					
						
							|  |  |  |             b'\r\n' | 
					
						
							|  |  |  |             b'-- Signed by Andr\xc3\xa9.\r\n' | 
					
						
							|  |  |  |             ), ascii(data)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_head(self): | 
					
						
							|  |  |  |         # HEAD | 
					
						
							|  |  |  |         resp, info = self.server.head() | 
					
						
							|  |  |  |         self.assertEqual(resp, "221 3000237 <45223423@example.com>") | 
					
						
							|  |  |  |         art_num, message_id, lines = info | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000237) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         self._check_article_head(lines) | 
					
						
							|  |  |  |         # HEAD num | 
					
						
							|  |  |  |         resp, info = self.server.head(3000234) | 
					
						
							|  |  |  |         self.assertEqual(resp, "221 3000234 <45223423@example.com>") | 
					
						
							|  |  |  |         art_num, message_id, lines = info | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000234) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         self._check_article_head(lines) | 
					
						
							|  |  |  |         # HEAD id | 
					
						
							|  |  |  |         resp, info = self.server.head("<45223423@example.com>") | 
					
						
							|  |  |  |         self.assertEqual(resp, "221 0 <45223423@example.com>") | 
					
						
							|  |  |  |         art_num, message_id, lines = info | 
					
						
							|  |  |  |         self.assertEqual(art_num, 0) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         self._check_article_head(lines) | 
					
						
							|  |  |  |         # Non-existent id | 
					
						
							|  |  |  |         with self.assertRaises(nntplib.NNTPTemporaryError) as cm: | 
					
						
							|  |  |  |             self.server.head("<non-existent@example.com>") | 
					
						
							|  |  |  |         self.assertEqual(cm.exception.response, "430 No Such Article Found") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-15 18:53:18 +01:00
										 |  |  |     def test_head_file(self): | 
					
						
							|  |  |  |         f = io.BytesIO() | 
					
						
							|  |  |  |         resp, info = self.server.head(file=f) | 
					
						
							|  |  |  |         self.assertEqual(resp, "221 3000237 <45223423@example.com>") | 
					
						
							|  |  |  |         art_num, message_id, lines = info | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000237) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         self.assertEqual(lines, []) | 
					
						
							|  |  |  |         data = f.getvalue() | 
					
						
							|  |  |  |         self.assertTrue(data.startswith( | 
					
						
							|  |  |  |             b'From: "Demo User" <nobody@example.net>\r\n' | 
					
						
							|  |  |  |             b'Subject: I am just a test article\r\n' | 
					
						
							|  |  |  |             ), ascii(data)) | 
					
						
							|  |  |  |         self.assertFalse(data.endswith( | 
					
						
							|  |  |  |             b'This is just a test article.\r\n' | 
					
						
							|  |  |  |             b'.Here is a dot-starting line.\r\n' | 
					
						
							|  |  |  |             b'\r\n' | 
					
						
							|  |  |  |             b'-- Signed by Andr\xc3\xa9.\r\n' | 
					
						
							|  |  |  |             ), ascii(data)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |     def test_body(self): | 
					
						
							|  |  |  |         # BODY | 
					
						
							|  |  |  |         resp, info = self.server.body() | 
					
						
							|  |  |  |         self.assertEqual(resp, "222 3000237 <45223423@example.com>") | 
					
						
							|  |  |  |         art_num, message_id, lines = info | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000237) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         self._check_article_body(lines) | 
					
						
							|  |  |  |         # BODY num | 
					
						
							|  |  |  |         resp, info = self.server.body(3000234) | 
					
						
							|  |  |  |         self.assertEqual(resp, "222 3000234 <45223423@example.com>") | 
					
						
							|  |  |  |         art_num, message_id, lines = info | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000234) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         self._check_article_body(lines) | 
					
						
							|  |  |  |         # BODY id | 
					
						
							|  |  |  |         resp, info = self.server.body("<45223423@example.com>") | 
					
						
							|  |  |  |         self.assertEqual(resp, "222 0 <45223423@example.com>") | 
					
						
							|  |  |  |         art_num, message_id, lines = info | 
					
						
							|  |  |  |         self.assertEqual(art_num, 0) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         self._check_article_body(lines) | 
					
						
							|  |  |  |         # Non-existent id | 
					
						
							|  |  |  |         with self.assertRaises(nntplib.NNTPTemporaryError) as cm: | 
					
						
							|  |  |  |             self.server.body("<non-existent@example.com>") | 
					
						
							|  |  |  |         self.assertEqual(cm.exception.response, "430 No Such Article Found") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-15 18:53:18 +01:00
										 |  |  |     def test_body_file(self): | 
					
						
							|  |  |  |         f = io.BytesIO() | 
					
						
							|  |  |  |         resp, info = self.server.body(file=f) | 
					
						
							|  |  |  |         self.assertEqual(resp, "222 3000237 <45223423@example.com>") | 
					
						
							|  |  |  |         art_num, message_id, lines = info | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000237) | 
					
						
							|  |  |  |         self.assertEqual(message_id, "<45223423@example.com>") | 
					
						
							|  |  |  |         self.assertEqual(lines, []) | 
					
						
							|  |  |  |         data = f.getvalue() | 
					
						
							|  |  |  |         self.assertFalse(data.startswith( | 
					
						
							|  |  |  |             b'From: "Demo User" <nobody@example.net>\r\n' | 
					
						
							|  |  |  |             b'Subject: I am just a test article\r\n' | 
					
						
							|  |  |  |             ), ascii(data)) | 
					
						
							|  |  |  |         self.assertTrue(data.endswith( | 
					
						
							|  |  |  |             b'This is just a test article.\r\n' | 
					
						
							|  |  |  |             b'.Here is a dot-starting line.\r\n' | 
					
						
							|  |  |  |             b'\r\n' | 
					
						
							|  |  |  |             b'-- Signed by Andr\xc3\xa9.\r\n' | 
					
						
							|  |  |  |             ), ascii(data)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |     def check_over_xover_resp(self, resp, overviews): | 
					
						
							|  |  |  |         self.assertTrue(resp.startswith("224 "), resp) | 
					
						
							|  |  |  |         self.assertEqual(len(overviews), 3) | 
					
						
							|  |  |  |         art_num, over = overviews[0] | 
					
						
							|  |  |  |         self.assertEqual(art_num, 57) | 
					
						
							|  |  |  |         self.assertEqual(over, { | 
					
						
							|  |  |  |             "from": "Doug Hellmann <doug.hellmann-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>", | 
					
						
							|  |  |  |             "subject": "Re: ANN: New Plone book with strong Python (and Zope) themes throughout", | 
					
						
							|  |  |  |             "date": "Sat, 19 Jun 2010 18:04:08 -0400", | 
					
						
							|  |  |  |             "message-id": "<4FD05F05-F98B-44DC-8111-C6009C925F0C@gmail.com>", | 
					
						
							|  |  |  |             "references": "<hvalf7$ort$1@dough.gmane.org>", | 
					
						
							|  |  |  |             ":bytes": "7103", | 
					
						
							|  |  |  |             ":lines": "16", | 
					
						
							| 
									
										
										
										
											2020-01-09 00:29:34 +09:00
										 |  |  |             "xref": "news.gmane.io gmane.comp.python.authors:57" | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2010-11-03 18:18:43 +00:00
										 |  |  |         art_num, over = overviews[1] | 
					
						
							|  |  |  |         self.assertEqual(over["xref"], None) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         art_num, over = overviews[2] | 
					
						
							|  |  |  |         self.assertEqual(over["subject"], | 
					
						
							|  |  |  |                          "Re: Message d'erreur incompréhensible (par moi)") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_xover(self): | 
					
						
							|  |  |  |         resp, overviews = self.server.xover(57, 59) | 
					
						
							|  |  |  |         self.check_over_xover_resp(resp, overviews) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_over(self): | 
					
						
							|  |  |  |         # In NNTP "v1", this will fallback on XOVER | 
					
						
							|  |  |  |         resp, overviews = self.server.over((57, 59)) | 
					
						
							|  |  |  |         self.check_over_xover_resp(resp, overviews) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sample_post = ( | 
					
						
							|  |  |  |         b'From: "Demo User" <nobody@example.net>\r\n' | 
					
						
							|  |  |  |         b'Subject: I am just a test article\r\n' | 
					
						
							|  |  |  |         b'Content-Type: text/plain; charset=UTF-8; format=flowed\r\n' | 
					
						
							|  |  |  |         b'Message-ID: <i.am.an.article.you.will.want@example.com>\r\n' | 
					
						
							|  |  |  |         b'\r\n' | 
					
						
							|  |  |  |         b'This is just a test article.\r\n' | 
					
						
							|  |  |  |         b'.Here is a dot-starting line.\r\n' | 
					
						
							|  |  |  |         b'\r\n' | 
					
						
							|  |  |  |         b'-- Signed by Andr\xc3\xa9.\r\n' | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _check_posted_body(self): | 
					
						
							|  |  |  |         # Check the raw body as received by the server | 
					
						
							|  |  |  |         lines = self.handler.posted_body | 
					
						
							|  |  |  |         # One additional line for the "." terminator | 
					
						
							|  |  |  |         self.assertEqual(len(lines), 10) | 
					
						
							|  |  |  |         self.assertEqual(lines[-1], b'.\r\n') | 
					
						
							|  |  |  |         self.assertEqual(lines[-2], b'-- Signed by Andr\xc3\xa9.\r\n') | 
					
						
							|  |  |  |         self.assertEqual(lines[-3], b'\r\n') | 
					
						
							|  |  |  |         self.assertEqual(lines[-4], b'..Here is a dot-starting line.\r\n') | 
					
						
							|  |  |  |         self.assertEqual(lines[0], b'From: "Demo User" <nobody@example.net>\r\n') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _check_post_ihave_sub(self, func, *args, file_factory): | 
					
						
							|  |  |  |         # First the prepared post with CRLF endings | 
					
						
							|  |  |  |         post = self.sample_post | 
					
						
							|  |  |  |         func_args = args + (file_factory(post),) | 
					
						
							|  |  |  |         self.handler.posted_body = None | 
					
						
							|  |  |  |         resp = func(*func_args) | 
					
						
							|  |  |  |         self._check_posted_body() | 
					
						
							|  |  |  |         # Then the same post with "normal" line endings - they should be | 
					
						
							|  |  |  |         # converted by NNTP.post and NNTP.ihave. | 
					
						
							|  |  |  |         post = self.sample_post.replace(b"\r\n", b"\n") | 
					
						
							|  |  |  |         func_args = args + (file_factory(post),) | 
					
						
							|  |  |  |         self.handler.posted_body = None | 
					
						
							|  |  |  |         resp = func(*func_args) | 
					
						
							|  |  |  |         self._check_posted_body() | 
					
						
							|  |  |  |         return resp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def check_post_ihave(self, func, success_resp, *args): | 
					
						
							|  |  |  |         # With a bytes object | 
					
						
							|  |  |  |         resp = self._check_post_ihave_sub(func, *args, file_factory=bytes) | 
					
						
							|  |  |  |         self.assertEqual(resp, success_resp) | 
					
						
							|  |  |  |         # With a bytearray object | 
					
						
							|  |  |  |         resp = self._check_post_ihave_sub(func, *args, file_factory=bytearray) | 
					
						
							|  |  |  |         self.assertEqual(resp, success_resp) | 
					
						
							|  |  |  |         # With a file object | 
					
						
							|  |  |  |         resp = self._check_post_ihave_sub(func, *args, file_factory=io.BytesIO) | 
					
						
							|  |  |  |         self.assertEqual(resp, success_resp) | 
					
						
							|  |  |  |         # With an iterable of terminated lines | 
					
						
							|  |  |  |         def iterlines(b): | 
					
						
							| 
									
										
										
										
											2011-09-28 17:37:55 +03:00
										 |  |  |             return iter(b.splitlines(keepends=True)) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         resp = self._check_post_ihave_sub(func, *args, file_factory=iterlines) | 
					
						
							|  |  |  |         self.assertEqual(resp, success_resp) | 
					
						
							|  |  |  |         # With an iterable of non-terminated lines | 
					
						
							|  |  |  |         def iterlines(b): | 
					
						
							| 
									
										
										
										
											2011-09-28 17:37:55 +03:00
										 |  |  |             return iter(b.splitlines(keepends=False)) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |         resp = self._check_post_ihave_sub(func, *args, file_factory=iterlines) | 
					
						
							|  |  |  |         self.assertEqual(resp, success_resp) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_post(self): | 
					
						
							|  |  |  |         self.check_post_ihave(self.server.post, "240 Article received OK") | 
					
						
							|  |  |  |         self.handler.allow_posting = False | 
					
						
							|  |  |  |         with self.assertRaises(nntplib.NNTPTemporaryError) as cm: | 
					
						
							|  |  |  |             self.server.post(self.sample_post) | 
					
						
							|  |  |  |         self.assertEqual(cm.exception.response, | 
					
						
							|  |  |  |                          "440 Posting not permitted") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_ihave(self): | 
					
						
							|  |  |  |         self.check_post_ihave(self.server.ihave, "235 Article transferred OK", | 
					
						
							|  |  |  |                               "<i.am.an.article.you.will.want@example.com>") | 
					
						
							|  |  |  |         with self.assertRaises(nntplib.NNTPTemporaryError) as cm: | 
					
						
							|  |  |  |             self.server.ihave("<another.message.id>", self.sample_post) | 
					
						
							|  |  |  |         self.assertEqual(cm.exception.response, | 
					
						
							|  |  |  |                          "435 Article not wanted") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-27 07:29:47 +01:00
										 |  |  |     def test_too_long_lines(self): | 
					
						
							|  |  |  |         dt = datetime.datetime(2010, 1, 1, 9, 0, 0) | 
					
						
							|  |  |  |         self.assertRaises(nntplib.NNTPDataError, | 
					
						
							|  |  |  |                           self.server.newnews, "comp.lang.python", dt) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | class NNTPv1Tests(NNTPv1v2TestsMixin, MockedNNTPTestsMixin, unittest.TestCase): | 
					
						
							|  |  |  |     """Tests an NNTP v1 server (no capabilities).""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     nntp_version = 1 | 
					
						
							|  |  |  |     handler_class = NNTPv1Handler | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_caps(self): | 
					
						
							|  |  |  |         caps = self.server.getcapabilities() | 
					
						
							|  |  |  |         self.assertEqual(caps, {}) | 
					
						
							|  |  |  |         self.assertEqual(self.server.nntp_version, 1) | 
					
						
							| 
									
										
										
										
											2010-11-05 19:16:37 +00:00
										 |  |  |         self.assertEqual(self.server.nntp_implementation, None) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class NNTPv2Tests(NNTPv1v2TestsMixin, MockedNNTPTestsMixin, unittest.TestCase): | 
					
						
							|  |  |  |     """Tests an NNTP v2 server (with capabilities).""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     nntp_version = 2 | 
					
						
							|  |  |  |     handler_class = NNTPv2Handler | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_caps(self): | 
					
						
							|  |  |  |         caps = self.server.getcapabilities() | 
					
						
							|  |  |  |         self.assertEqual(caps, { | 
					
						
							| 
									
										
										
										
											2010-11-02 22:31:52 +00:00
										 |  |  |             'VERSION': ['2', '3'], | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  |             'IMPLEMENTATION': ['INN', '2.5.1'], | 
					
						
							|  |  |  |             'AUTHINFO': ['USER'], | 
					
						
							|  |  |  |             'HDR': [], | 
					
						
							|  |  |  |             'LIST': ['ACTIVE', 'ACTIVE.TIMES', 'DISTRIB.PATS', | 
					
						
							|  |  |  |                      'HEADERS', 'NEWSGROUPS', 'OVERVIEW.FMT'], | 
					
						
							|  |  |  |             'OVER': [], | 
					
						
							|  |  |  |             'POST': [], | 
					
						
							|  |  |  |             'READER': [], | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2010-11-02 22:31:52 +00:00
										 |  |  |         self.assertEqual(self.server.nntp_version, 3) | 
					
						
							| 
									
										
										
										
											2010-11-05 19:16:37 +00:00
										 |  |  |         self.assertEqual(self.server.nntp_implementation, 'INN 2.5.1') | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-12 19:14:17 +01:00
										 |  |  | class CapsAfterLoginNNTPv2Tests(MockedNNTPTestsMixin, unittest.TestCase): | 
					
						
							|  |  |  |     """Tests a probably NNTP v2 server with capabilities only after login.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     nntp_version = 2 | 
					
						
							|  |  |  |     handler_class = CapsAfterLoginNNTPv2Handler | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_caps_only_after_login(self): | 
					
						
							|  |  |  |         self.assertEqual(self.server._caps, {}) | 
					
						
							|  |  |  |         self.server.login('testuser', 'testpw') | 
					
						
							|  |  |  |         self.assertIn('VERSION', self.server._caps) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-14 23:29:34 +01:00
										 |  |  | class SendReaderNNTPv2Tests(MockedNNTPWithReaderModeMixin, | 
					
						
							|  |  |  |         unittest.TestCase): | 
					
						
							|  |  |  |     """Same tests as for v2 but we tell NTTP to send MODE READER to a server
 | 
					
						
							|  |  |  |     that isn't in READER mode by default.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     nntp_version = 2 | 
					
						
							|  |  |  |     handler_class = ModeSwitchingNNTPv2Handler | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_we_are_in_reader_mode_after_connect(self): | 
					
						
							|  |  |  |         self.assertIn('READER', self.server._caps) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | class MiscTests(unittest.TestCase): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_decode_header(self): | 
					
						
							|  |  |  |         def gives(a, b): | 
					
						
							|  |  |  |             self.assertEqual(nntplib.decode_header(a), b) | 
					
						
							|  |  |  |         gives("" , "") | 
					
						
							|  |  |  |         gives("a plain header", "a plain header") | 
					
						
							|  |  |  |         gives(" with extra  spaces ", " with extra  spaces ") | 
					
						
							|  |  |  |         gives("=?ISO-8859-15?Q?D=E9buter_en_Python?=", "Débuter en Python") | 
					
						
							|  |  |  |         gives("=?utf-8?q?Re=3A_=5Bsqlite=5D_probl=C3=A8me_avec_ORDER_BY_sur_des_cha?=" | 
					
						
							|  |  |  |               " =?utf-8?q?=C3=AEnes_de_caract=C3=A8res_accentu=C3=A9es?=", | 
					
						
							|  |  |  |               "Re: [sqlite] problème avec ORDER BY sur des chaînes de caractères accentuées") | 
					
						
							|  |  |  |         gives("Re: =?UTF-8?B?cHJvYmzDqG1lIGRlIG1hdHJpY2U=?=", | 
					
						
							|  |  |  |               "Re: problème de matrice") | 
					
						
							|  |  |  |         # A natively utf-8 header (found in the real world!) | 
					
						
							|  |  |  |         gives("Re: Message d'erreur incompréhensible (par moi)", | 
					
						
							|  |  |  |               "Re: Message d'erreur incompréhensible (par moi)") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_parse_overview_fmt(self): | 
					
						
							|  |  |  |         # The minimal (default) response | 
					
						
							|  |  |  |         lines = ["Subject:", "From:", "Date:", "Message-ID:", | 
					
						
							|  |  |  |                  "References:", ":bytes", ":lines"] | 
					
						
							|  |  |  |         self.assertEqual(nntplib._parse_overview_fmt(lines), | 
					
						
							|  |  |  |             ["subject", "from", "date", "message-id", "references", | 
					
						
							|  |  |  |              ":bytes", ":lines"]) | 
					
						
							|  |  |  |         # The minimal response using alternative names | 
					
						
							|  |  |  |         lines = ["Subject:", "From:", "Date:", "Message-ID:", | 
					
						
							|  |  |  |                  "References:", "Bytes:", "Lines:"] | 
					
						
							|  |  |  |         self.assertEqual(nntplib._parse_overview_fmt(lines), | 
					
						
							|  |  |  |             ["subject", "from", "date", "message-id", "references", | 
					
						
							|  |  |  |              ":bytes", ":lines"]) | 
					
						
							|  |  |  |         # Variations in casing | 
					
						
							|  |  |  |         lines = ["subject:", "FROM:", "DaTe:", "message-ID:", | 
					
						
							|  |  |  |                  "References:", "BYTES:", "Lines:"] | 
					
						
							|  |  |  |         self.assertEqual(nntplib._parse_overview_fmt(lines), | 
					
						
							|  |  |  |             ["subject", "from", "date", "message-id", "references", | 
					
						
							|  |  |  |              ":bytes", ":lines"]) | 
					
						
							|  |  |  |         # First example from RFC 3977 | 
					
						
							|  |  |  |         lines = ["Subject:", "From:", "Date:", "Message-ID:", | 
					
						
							|  |  |  |                  "References:", ":bytes", ":lines", "Xref:full", | 
					
						
							|  |  |  |                  "Distribution:full"] | 
					
						
							|  |  |  |         self.assertEqual(nntplib._parse_overview_fmt(lines), | 
					
						
							|  |  |  |             ["subject", "from", "date", "message-id", "references", | 
					
						
							|  |  |  |              ":bytes", ":lines", "xref", "distribution"]) | 
					
						
							|  |  |  |         # Second example from RFC 3977 | 
					
						
							|  |  |  |         lines = ["Subject:", "From:", "Date:", "Message-ID:", | 
					
						
							|  |  |  |                  "References:", "Bytes:", "Lines:", "Xref:FULL", | 
					
						
							|  |  |  |                  "Distribution:FULL"] | 
					
						
							|  |  |  |         self.assertEqual(nntplib._parse_overview_fmt(lines), | 
					
						
							|  |  |  |             ["subject", "from", "date", "message-id", "references", | 
					
						
							|  |  |  |              ":bytes", ":lines", "xref", "distribution"]) | 
					
						
							|  |  |  |         # A classic response from INN | 
					
						
							|  |  |  |         lines = ["Subject:", "From:", "Date:", "Message-ID:", | 
					
						
							|  |  |  |                  "References:", "Bytes:", "Lines:", "Xref:full"] | 
					
						
							|  |  |  |         self.assertEqual(nntplib._parse_overview_fmt(lines), | 
					
						
							|  |  |  |             ["subject", "from", "date", "message-id", "references", | 
					
						
							|  |  |  |              ":bytes", ":lines", "xref"]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_parse_overview(self): | 
					
						
							|  |  |  |         fmt = nntplib._DEFAULT_OVERVIEW_FMT + ["xref"] | 
					
						
							|  |  |  |         # First example from RFC 3977 | 
					
						
							|  |  |  |         lines = [ | 
					
						
							|  |  |  |             '3000234\tI am just a test article\t"Demo User" ' | 
					
						
							|  |  |  |             '<nobody@example.com>\t6 Oct 1998 04:38:40 -0500\t' | 
					
						
							|  |  |  |             '<45223423@example.com>\t<45454@example.net>\t1234\t' | 
					
						
							|  |  |  |             '17\tXref: news.example.com misc.test:3000363', | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |         overview = nntplib._parse_overview(lines, fmt) | 
					
						
							|  |  |  |         (art_num, fields), = overview | 
					
						
							|  |  |  |         self.assertEqual(art_num, 3000234) | 
					
						
							|  |  |  |         self.assertEqual(fields, { | 
					
						
							|  |  |  |             'subject': 'I am just a test article', | 
					
						
							|  |  |  |             'from': '"Demo User" <nobody@example.com>', | 
					
						
							|  |  |  |             'date': '6 Oct 1998 04:38:40 -0500', | 
					
						
							|  |  |  |             'message-id': '<45223423@example.com>', | 
					
						
							|  |  |  |             'references': '<45454@example.net>', | 
					
						
							|  |  |  |             ':bytes': '1234', | 
					
						
							|  |  |  |             ':lines': '17', | 
					
						
							|  |  |  |             'xref': 'news.example.com misc.test:3000363', | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2010-11-03 18:18:43 +00:00
										 |  |  |         # Second example; here the "Xref" field is totally absent (including | 
					
						
							|  |  |  |         # the header name) and comes out as None | 
					
						
							|  |  |  |         lines = [ | 
					
						
							|  |  |  |             '3000234\tI am just a test article\t"Demo User" ' | 
					
						
							|  |  |  |             '<nobody@example.com>\t6 Oct 1998 04:38:40 -0500\t' | 
					
						
							|  |  |  |             '<45223423@example.com>\t<45454@example.net>\t1234\t' | 
					
						
							|  |  |  |             '17\t\t', | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |         overview = nntplib._parse_overview(lines, fmt) | 
					
						
							|  |  |  |         (art_num, fields), = overview | 
					
						
							|  |  |  |         self.assertEqual(fields['xref'], None) | 
					
						
							|  |  |  |         # Third example; the "Xref" is an empty string, while "references" | 
					
						
							|  |  |  |         # is a single space. | 
					
						
							|  |  |  |         lines = [ | 
					
						
							|  |  |  |             '3000234\tI am just a test article\t"Demo User" ' | 
					
						
							|  |  |  |             '<nobody@example.com>\t6 Oct 1998 04:38:40 -0500\t' | 
					
						
							|  |  |  |             '<45223423@example.com>\t \t1234\t' | 
					
						
							|  |  |  |             '17\tXref: \t', | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |         overview = nntplib._parse_overview(lines, fmt) | 
					
						
							|  |  |  |         (art_num, fields), = overview | 
					
						
							|  |  |  |         self.assertEqual(fields['references'], ' ') | 
					
						
							|  |  |  |         self.assertEqual(fields['xref'], '') | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_parse_datetime(self): | 
					
						
							|  |  |  |         def gives(a, b, *c): | 
					
						
							|  |  |  |             self.assertEqual(nntplib._parse_datetime(a, b), | 
					
						
							|  |  |  |                              datetime.datetime(*c)) | 
					
						
							|  |  |  |         # Output of DATE command | 
					
						
							|  |  |  |         gives("19990623135624", None, 1999, 6, 23, 13, 56, 24) | 
					
						
							|  |  |  |         # Variations | 
					
						
							|  |  |  |         gives("19990623", "135624", 1999, 6, 23, 13, 56, 24) | 
					
						
							|  |  |  |         gives("990623", "135624", 1999, 6, 23, 13, 56, 24) | 
					
						
							|  |  |  |         gives("090623", "135624", 2009, 6, 23, 13, 56, 24) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_unparse_datetime(self): | 
					
						
							|  |  |  |         # Test non-legacy mode | 
					
						
							|  |  |  |         # 1) with a datetime | 
					
						
							|  |  |  |         def gives(y, M, d, h, m, s, date_str, time_str): | 
					
						
							|  |  |  |             dt = datetime.datetime(y, M, d, h, m, s) | 
					
						
							|  |  |  |             self.assertEqual(nntplib._unparse_datetime(dt), | 
					
						
							|  |  |  |                              (date_str, time_str)) | 
					
						
							|  |  |  |             self.assertEqual(nntplib._unparse_datetime(dt, False), | 
					
						
							|  |  |  |                              (date_str, time_str)) | 
					
						
							|  |  |  |         gives(1999, 6, 23, 13, 56, 24, "19990623", "135624") | 
					
						
							|  |  |  |         gives(2000, 6, 23, 13, 56, 24, "20000623", "135624") | 
					
						
							|  |  |  |         gives(2010, 6, 5, 1, 2, 3, "20100605", "010203") | 
					
						
							|  |  |  |         # 2) with a date | 
					
						
							|  |  |  |         def gives(y, M, d, date_str, time_str): | 
					
						
							|  |  |  |             dt = datetime.date(y, M, d) | 
					
						
							|  |  |  |             self.assertEqual(nntplib._unparse_datetime(dt), | 
					
						
							|  |  |  |                              (date_str, time_str)) | 
					
						
							|  |  |  |             self.assertEqual(nntplib._unparse_datetime(dt, False), | 
					
						
							|  |  |  |                              (date_str, time_str)) | 
					
						
							|  |  |  |         gives(1999, 6, 23, "19990623", "000000") | 
					
						
							|  |  |  |         gives(2000, 6, 23, "20000623", "000000") | 
					
						
							|  |  |  |         gives(2010, 6, 5, "20100605", "000000") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_unparse_datetime_legacy(self): | 
					
						
							|  |  |  |         # Test legacy mode (RFC 977) | 
					
						
							|  |  |  |         # 1) with a datetime | 
					
						
							|  |  |  |         def gives(y, M, d, h, m, s, date_str, time_str): | 
					
						
							|  |  |  |             dt = datetime.datetime(y, M, d, h, m, s) | 
					
						
							|  |  |  |             self.assertEqual(nntplib._unparse_datetime(dt, True), | 
					
						
							|  |  |  |                              (date_str, time_str)) | 
					
						
							|  |  |  |         gives(1999, 6, 23, 13, 56, 24, "990623", "135624") | 
					
						
							|  |  |  |         gives(2000, 6, 23, 13, 56, 24, "000623", "135624") | 
					
						
							|  |  |  |         gives(2010, 6, 5, 1, 2, 3, "100605", "010203") | 
					
						
							|  |  |  |         # 2) with a date | 
					
						
							|  |  |  |         def gives(y, M, d, date_str, time_str): | 
					
						
							|  |  |  |             dt = datetime.date(y, M, d) | 
					
						
							|  |  |  |             self.assertEqual(nntplib._unparse_datetime(dt, True), | 
					
						
							|  |  |  |                              (date_str, time_str)) | 
					
						
							|  |  |  |         gives(1999, 6, 23, "990623", "000000") | 
					
						
							|  |  |  |         gives(2000, 6, 23, "000623", "000000") | 
					
						
							|  |  |  |         gives(2010, 6, 5, "100605", "000000") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-03 21:31:38 +02:00
										 |  |  |     @unittest.skipUnless(ssl, 'requires SSL support') | 
					
						
							|  |  |  |     def test_ssl_support(self): | 
					
						
							|  |  |  |         self.assertTrue(hasattr(nntplib, 'NNTP_SSL')) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-09-20 08:53:05 +03:00
										 |  |  | class PublicAPITests(unittest.TestCase): | 
					
						
							|  |  |  |     """Ensures that the correct values are exposed in the public API.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_module_all_attribute(self): | 
					
						
							|  |  |  |         self.assertTrue(hasattr(nntplib, '__all__')) | 
					
						
							|  |  |  |         target_api = ['NNTP', 'NNTPError', 'NNTPReplyError', | 
					
						
							|  |  |  |                       'NNTPTemporaryError', 'NNTPPermanentError', | 
					
						
							|  |  |  |                       'NNTPProtocolError', 'NNTPDataError', 'decode_header'] | 
					
						
							|  |  |  |         if ssl is not None: | 
					
						
							|  |  |  |             target_api.append('NNTP_SSL') | 
					
						
							|  |  |  |         self.assertEqual(set(nntplib.__all__), set(target_api)) | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-21 09:40:26 +02:00
										 |  |  | class MockSocketTests(unittest.TestCase): | 
					
						
							|  |  |  |     """Tests involving a mock socket object
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Used where the _NNTPServerIO file object is not enough."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     nntp_class = nntplib.NNTP | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def check_constructor_error_conditions( | 
					
						
							|  |  |  |             self, handler_class, | 
					
						
							|  |  |  |             expected_error_type, expected_error_msg, | 
					
						
							|  |  |  |             login=None, password=None): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         class mock_socket_module: | 
					
						
							|  |  |  |             def create_connection(address, timeout): | 
					
						
							|  |  |  |                 return MockSocket() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         class MockSocket: | 
					
						
							|  |  |  |             def close(self): | 
					
						
							|  |  |  |                 nonlocal socket_closed | 
					
						
							|  |  |  |                 socket_closed = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def makefile(socket, mode): | 
					
						
							|  |  |  |                 handler = handler_class() | 
					
						
							|  |  |  |                 _, file = make_mock_file(handler) | 
					
						
							|  |  |  |                 files.append(file) | 
					
						
							|  |  |  |                 return file | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         socket_closed = False | 
					
						
							|  |  |  |         files = [] | 
					
						
							|  |  |  |         with patch('nntplib.socket', mock_socket_module), \ | 
					
						
							|  |  |  |              self.assertRaisesRegex(expected_error_type, expected_error_msg): | 
					
						
							|  |  |  |             self.nntp_class('dummy', user=login, password=password) | 
					
						
							|  |  |  |         self.assertTrue(socket_closed) | 
					
						
							|  |  |  |         for f in files: | 
					
						
							|  |  |  |             self.assertTrue(f.closed) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_bad_welcome(self): | 
					
						
							|  |  |  |         #Test a bad welcome message | 
					
						
							|  |  |  |         class Handler(NNTPv1Handler): | 
					
						
							|  |  |  |             welcome = 'Bad Welcome' | 
					
						
							|  |  |  |         self.check_constructor_error_conditions( | 
					
						
							|  |  |  |             Handler, nntplib.NNTPProtocolError, Handler.welcome) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_service_temporarily_unavailable(self): | 
					
						
							|  |  |  |         #Test service temporarily unavailable | 
					
						
							|  |  |  |         class Handler(NNTPv1Handler): | 
					
						
							| 
									
										
										
										
											2016-07-28 01:11:04 +00:00
										 |  |  |             welcome = '400 Service temporarily unavailable' | 
					
						
							| 
									
										
										
										
											2015-03-21 09:40:26 +02:00
										 |  |  |         self.check_constructor_error_conditions( | 
					
						
							|  |  |  |             Handler, nntplib.NNTPTemporaryError, Handler.welcome) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_service_permanently_unavailable(self): | 
					
						
							|  |  |  |         #Test service permanently unavailable | 
					
						
							|  |  |  |         class Handler(NNTPv1Handler): | 
					
						
							| 
									
										
										
										
											2016-07-28 01:11:04 +00:00
										 |  |  |             welcome = '502 Service permanently unavailable' | 
					
						
							| 
									
										
										
										
											2015-03-21 09:40:26 +02:00
										 |  |  |         self.check_constructor_error_conditions( | 
					
						
							|  |  |  |             Handler, nntplib.NNTPPermanentError, Handler.welcome) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_bad_capabilities(self): | 
					
						
							|  |  |  |         #Test a bad capabilities response | 
					
						
							|  |  |  |         class Handler(NNTPv1Handler): | 
					
						
							|  |  |  |             def handle_CAPABILITIES(self): | 
					
						
							|  |  |  |                 self.push_lit(capabilities_response) | 
					
						
							|  |  |  |         capabilities_response = '201 bad capability' | 
					
						
							|  |  |  |         self.check_constructor_error_conditions( | 
					
						
							|  |  |  |             Handler, nntplib.NNTPReplyError, capabilities_response) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_login_aborted(self): | 
					
						
							|  |  |  |         #Test a bad authinfo response | 
					
						
							|  |  |  |         login = 't@e.com' | 
					
						
							|  |  |  |         password = 'python' | 
					
						
							|  |  |  |         class Handler(NNTPv1Handler): | 
					
						
							|  |  |  |             def handle_AUTHINFO(self, *args): | 
					
						
							|  |  |  |                 self.push_lit(authinfo_response) | 
					
						
							|  |  |  |         authinfo_response = '503 Mechanism not recognized' | 
					
						
							|  |  |  |         self.check_constructor_error_conditions( | 
					
						
							|  |  |  |             Handler, nntplib.NNTPPermanentError, authinfo_response, | 
					
						
							|  |  |  |             login, password) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-03 15:02:20 +03:00
										 |  |  | class bypass_context: | 
					
						
							|  |  |  |     """Bypass encryption and actual SSL module""" | 
					
						
							|  |  |  |     def wrap_socket(sock, **args): | 
					
						
							|  |  |  |         return sock | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @unittest.skipUnless(ssl, 'requires SSL support') | 
					
						
							|  |  |  | class MockSslTests(MockSocketTests): | 
					
						
							|  |  |  |     @staticmethod | 
					
						
							|  |  |  |     def nntp_class(*pos, **kw): | 
					
						
							|  |  |  |         return nntplib.NNTP_SSL(*pos, ssl_context=bypass_context, **kw) | 
					
						
							| 
									
										
										
										
											2015-04-03 11:06:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-07 18:56:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-19 01:10:58 +00:00
										 |  |  | class LocalServerTests(unittest.TestCase): | 
					
						
							|  |  |  |     def setUp(self): | 
					
						
							|  |  |  |         sock = socket.socket() | 
					
						
							| 
									
										
										
										
											2020-04-25 10:06:29 +03:00
										 |  |  |         port = socket_helper.bind_port(sock) | 
					
						
							| 
									
										
										
										
											2016-01-19 01:10:58 +00:00
										 |  |  |         sock.listen() | 
					
						
							|  |  |  |         self.background = threading.Thread( | 
					
						
							|  |  |  |             target=self.run_server, args=(sock,)) | 
					
						
							|  |  |  |         self.background.start() | 
					
						
							|  |  |  |         self.addCleanup(self.background.join) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-25 10:06:29 +03:00
										 |  |  |         self.nntp = NNTP(socket_helper.HOST, port, usenetrc=False).__enter__() | 
					
						
							| 
									
										
										
										
											2016-01-19 01:10:58 +00:00
										 |  |  |         self.addCleanup(self.nntp.__exit__, None, None, None) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def run_server(self, sock): | 
					
						
							|  |  |  |         # Could be generalized to handle more commands in separate methods | 
					
						
							|  |  |  |         with sock: | 
					
						
							|  |  |  |             [client, _] = sock.accept() | 
					
						
							|  |  |  |         with contextlib.ExitStack() as cleanup: | 
					
						
							|  |  |  |             cleanup.enter_context(client) | 
					
						
							|  |  |  |             reader = cleanup.enter_context(client.makefile('rb')) | 
					
						
							|  |  |  |             client.sendall(b'200 Server ready\r\n') | 
					
						
							|  |  |  |             while True: | 
					
						
							|  |  |  |                 cmd = reader.readline() | 
					
						
							|  |  |  |                 if cmd == b'CAPABILITIES\r\n': | 
					
						
							|  |  |  |                     client.sendall( | 
					
						
							|  |  |  |                         b'101 Capability list:\r\n' | 
					
						
							|  |  |  |                         b'VERSION 2\r\n' | 
					
						
							|  |  |  |                         b'STARTTLS\r\n' | 
					
						
							|  |  |  |                         b'.\r\n' | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 elif cmd == b'STARTTLS\r\n': | 
					
						
							|  |  |  |                     reader.close() | 
					
						
							|  |  |  |                     client.sendall(b'382 Begin TLS negotiation now\r\n') | 
					
						
							| 
									
										
										
										
											2016-09-10 23:23:33 +02:00
										 |  |  |                     context = ssl.SSLContext() | 
					
						
							|  |  |  |                     context.load_cert_chain(certfile) | 
					
						
							|  |  |  |                     client = context.wrap_socket( | 
					
						
							|  |  |  |                         client, server_side=True) | 
					
						
							| 
									
										
										
										
											2016-01-19 01:10:58 +00:00
										 |  |  |                     cleanup.enter_context(client) | 
					
						
							|  |  |  |                     reader = cleanup.enter_context(client.makefile('rb')) | 
					
						
							|  |  |  |                 elif cmd == b'QUIT\r\n': | 
					
						
							|  |  |  |                     client.sendall(b'205 Bye!\r\n') | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     raise ValueError('Unexpected command {!r}'.format(cmd)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @unittest.skipUnless(ssl, 'requires SSL support') | 
					
						
							|  |  |  |     def test_starttls(self): | 
					
						
							|  |  |  |         file = self.nntp.file | 
					
						
							|  |  |  |         sock = self.nntp.sock | 
					
						
							|  |  |  |         self.nntp.starttls() | 
					
						
							|  |  |  |         # Check that the socket and internal pseudo-file really were | 
					
						
							|  |  |  |         # changed. | 
					
						
							|  |  |  |         self.assertNotEqual(file, self.nntp.file) | 
					
						
							|  |  |  |         self.assertNotEqual(sock, self.nntp.sock) | 
					
						
							|  |  |  |         # Check that the new socket really is an SSL one | 
					
						
							|  |  |  |         self.assertIsInstance(self.nntp.sock, ssl.SSLSocket) | 
					
						
							|  |  |  |         # Check that trying starttls when it's already active fails. | 
					
						
							|  |  |  |         self.assertRaises(ValueError, self.nntp.starttls) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-21 09:40:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-09-29 15:03:40 +00:00
										 |  |  | if __name__ == "__main__": | 
					
						
							| 
									
										
										
										
											2014-09-20 08:53:05 +03:00
										 |  |  |     unittest.main() |