Commit 1cb121ec authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #1926: Add support for NNTP over SSL on port 563, as well as

STARTTLS.  Patch by Andrew Vant.
parent 14fb7997
......@@ -69,6 +69,22 @@ The module itself defines the following classes:
*readermode* defaults to ``None``. *usenetrc* defaults to ``True``.
.. class:: NNTP_SSL(host, port=563, user=None, password=None, ssl_context=None, readermode=None, usenetrc=True, [timeout])
Return a new :class:`NNTP_SSL` object, representing an encrypted
connection to the NNTP server running on host *host*, listening at
port *port*. :class:`NNTP_SSL` objects have the same methods as
:class:`NNTP` objects. If *port* is omitted, port 563 (NNTPS) is used.
*ssl_context* is also optional, and is a :class:`~ssl.SSLContext` object.
All other parameters behave the same as for :class:`NNTP`.
Note that SSL-on-563 is discouraged per :rfc:`4642`, in favor of
STARTTLS as described below. However, some servers only support the
former.
.. versionadded:: 3.2
.. exception:: NNTPError
Derived from the standard exception :exc:`Exception`, this is the base
......@@ -111,8 +127,8 @@ The module itself defines the following classes:
NNTP Objects
------------
When connected, :class:`NNTP` objects support the following methods and
attributes.
When connected, :class:`NNTP` and :class:`NNTP_SSL` objects support the
following methods and attributes.
Attributes
^^^^^^^^^^
......@@ -179,6 +195,35 @@ tuples or objects that the method normally returns will be empty.
.. versionadded:: 3.2
.. method:: NNTP.login(user=None, password=None, usenetrc=True)
Send ``AUTHINFO`` commands with the user name and password. If *user*
and *password* are None and *usenetrc* is True, credentials from
``~/.netrc`` will be used if possible.
Unless intentionally delayed, login is normally performed during the
:class:`NNTP` object initialization and separately calling this function
is unnecessary. To force authentication to be delayed, you must not set
*user* or *password* when creating the object, and must set *usenetrc* to
False.
.. versionadded:: 3.2
.. method:: NNTP.starttls(ssl_context=None)
Send a ``STARTTLS`` command. The *ssl_context* argument is optional
and should be a :class:`ssl.SSLContext` object. This will enable
encryption on the NNTP connection.
Note that this may not be done after authentication information has
been transmitted, and authentication occurs by default if possible during a
:class:`NNTP` object initialization. See :meth:`NNTP.login` for information
on suppressing this behavior.
.. versionadded:: 3.2
.. method:: NNTP.newgroups(date, *, file=None)
Send a ``NEWGROUPS`` command. The *date* argument should be a
......
This diff is collapsed.
......@@ -4,8 +4,10 @@ import textwrap
import unittest
import contextlib
from test import support
from nntplib import NNTP, GroupInfo
from nntplib import NNTP, GroupInfo, _have_ssl
import nntplib
if _have_ssl:
import ssl
TIMEOUT = 30
......@@ -106,7 +108,7 @@ class NetworkedNNTPTestsMixin:
"references", ":bytes", ":lines"}
)
for v in art_dict.values():
self.assertIsInstance(v, str)
self.assertIsInstance(v, (str, type(None)))
def test_xover(self):
resp, count, first, last, name = self.server.group(self.GROUP_NAME)
......@@ -162,26 +164,19 @@ class NetworkedNNTPTestsMixin:
self.server.quit()
self.server = None
class NetworkedNNTPTests(NetworkedNNTPTestsMixin, unittest.TestCase):
NNTP_HOST = 'news.gmane.org'
GROUP_NAME = 'gmane.comp.python.devel'
GROUP_PAT = 'gmane.comp.python.d*'
def setUp(self):
support.requires("network")
with support.transient_internet(self.NNTP_HOST):
self.server = NNTP(self.NNTP_HOST, timeout=TIMEOUT, usenetrc=False)
def tearDown(self):
if self.server is not None:
self.server.quit()
# Disabled with gmane as it produces too much data
test_list = None
def test_login(self):
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_capabilities(self):
# As of this writing, gmane implements NNTP version 2 and has a
# The server under test implements NNTP version 2 and has a
# couple of well-known capabilities. Just sanity check that we
# got them.
def _check_caps(caps):
......@@ -194,6 +189,63 @@ class NetworkedNNTPTests(NetworkedNNTPTestsMixin, unittest.TestCase):
resp, caps = self.server.capabilities()
_check_caps(caps)
if _have_ssl:
def test_starttls(self):
file = self.server.file
sock = self.server.sock
try:
self.server.starttls()
except nntplib.NNTPPermanentError:
self.skipTest("STARTTLS not supported by server.")
else:
# Check that the socket and internal pseudo-file really were
# changed.
self.assertNotEqual(file, self.server.file)
self.assertNotEqual(sock, self.server.sock)
# Check that the new socket really is an SSL one
self.assertIsInstance(self.server.sock, ssl.SSLSocket)
# Check that trying starttls when it's already active fails.
self.assertRaises(ValueError, self.server.starttls)
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.*'
def setUp(self):
support.requires("network")
with support.transient_internet(self.NNTP_HOST):
self.server = NNTP(self.NNTP_HOST, timeout=TIMEOUT, usenetrc=False)
def tearDown(self):
if self.server is not None:
self.server.quit()
if _have_ssl:
class NetworkedNNTP_SSLTests(NetworkedNNTPTestsMixin, unittest.TestCase):
NNTP_HOST = 'snews.gmane.org'
GROUP_NAME = 'gmane.comp.python.devel'
GROUP_PAT = 'gmane.comp.python.d*'
def setUp(self):
support.requires("network")
with support.transient_internet(self.NNTP_HOST):
self.server = nntplib.NNTP_SSL(self.NNTP_HOST, timeout=TIMEOUT,
usenetrc=False)
def tearDown(self):
if self.server is not None:
self.server.quit()
# Disabled with gmane as it produces too much data
test_list = None
# Disabled as the connection will already be encrypted.
test_starttls = None
#
# Non-networked tests using a local server (or something mocking it).
......@@ -261,7 +313,6 @@ class MockedNNTPTestsMixin:
# Using BufferedRWPair instead of BufferedRandom ensures the file
# isn't seekable.
file = io.BufferedRWPair(self.sio, self.sio)
kwargs.setdefault('usenetrc', False)
self.server = nntplib._NNTPBase(file, 'test.server', *args, **kwargs)
return self.server
......@@ -1134,9 +1185,10 @@ class MiscTests(unittest.TestCase):
def test_main():
support.run_unittest(MiscTests, NNTPv1Tests, NNTPv2Tests,
NetworkedNNTPTests
)
tests = [MiscTests, NNTPv1Tests, NNTPv2Tests, NetworkedNNTPTests]
if _have_ssl:
tests.append(NetworkedNNTP_SSLTests)
support.run_unittest(*tests)
if __name__ == "__main__":
......
......@@ -843,6 +843,7 @@ Hector Urtubia
Andi Vajda
Case Van Horsen
Kyle VanderBeek
Andrew Vant
Atul Varma
Dmitry Vasiliev
Alexandre Vassalotti
......
......@@ -60,6 +60,9 @@ Core and Builtins
Library
-------
- Issue #1926: Add support for NNTP over SSL on port 563, as well as
STARTTLS. Patch by Andrew Vant.
- Issue #10335: Add tokenize.open(), detect the file encoding using
tokenize.detect_encoding() and open it in read only mode.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment