Commit 8676650b 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 e5b67d29
...@@ -69,6 +69,22 @@ The module itself defines the following classes: ...@@ -69,6 +69,22 @@ The module itself defines the following classes:
*readermode* defaults to ``None``. *usenetrc* defaults to ``True``. *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 .. exception:: NNTPError
Derived from the standard exception :exc:`Exception`, this is the base Derived from the standard exception :exc:`Exception`, this is the base
...@@ -111,8 +127,8 @@ The module itself defines the following classes: ...@@ -111,8 +127,8 @@ The module itself defines the following classes:
NNTP Objects NNTP Objects
------------ ------------
When connected, :class:`NNTP` objects support the following methods and When connected, :class:`NNTP` and :class:`NNTP_SSL` objects support the
attributes. following methods and attributes.
Attributes Attributes
^^^^^^^^^^ ^^^^^^^^^^
...@@ -179,6 +195,35 @@ tuples or objects that the method normally returns will be empty. ...@@ -179,6 +195,35 @@ tuples or objects that the method normally returns will be empty.
.. versionadded:: 3.2 .. 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) .. method:: NNTP.newgroups(date, *, file=None)
Send a ``NEWGROUPS`` command. The *date* argument should be a Send a ``NEWGROUPS`` command. The *date* argument should be a
......
This diff is collapsed.
...@@ -4,8 +4,10 @@ import textwrap ...@@ -4,8 +4,10 @@ import textwrap
import unittest import unittest
import contextlib import contextlib
from test import support from test import support
from nntplib import NNTP, GroupInfo from nntplib import NNTP, GroupInfo, _have_ssl
import nntplib import nntplib
if _have_ssl:
import ssl
TIMEOUT = 30 TIMEOUT = 30
...@@ -106,7 +108,7 @@ class NetworkedNNTPTestsMixin: ...@@ -106,7 +108,7 @@ class NetworkedNNTPTestsMixin:
"references", ":bytes", ":lines"} "references", ":bytes", ":lines"}
) )
for v in art_dict.values(): for v in art_dict.values():
self.assertIsInstance(v, str) self.assertIsInstance(v, (str, type(None)))
def test_xover(self): def test_xover(self):
resp, count, first, last, name = self.server.group(self.GROUP_NAME) resp, count, first, last, name = self.server.group(self.GROUP_NAME)
...@@ -162,26 +164,19 @@ class NetworkedNNTPTestsMixin: ...@@ -162,26 +164,19 @@ class NetworkedNNTPTestsMixin:
self.server.quit() self.server.quit()
self.server = None self.server = None
def test_login(self):
class NetworkedNNTPTests(NetworkedNNTPTestsMixin, unittest.TestCase): baduser = "notarealuser"
NNTP_HOST = 'news.gmane.org' badpw = "notarealpassword"
GROUP_NAME = 'gmane.comp.python.devel' # Check that bogus credentials cause failure
GROUP_PAT = 'gmane.comp.python.d*' self.assertRaises(nntplib.NNTPError, self.server.login,
user=baduser, password=badpw, usenetrc=False)
def setUp(self): # FIXME: We should check that correct credentials succeed, but that
support.requires("network") # would require valid details for some server somewhere to be in the
with support.transient_internet(self.NNTP_HOST): # test suite, I think. Gmane is anonymous, at least as used for the
self.server = NNTP(self.NNTP_HOST, timeout=TIMEOUT, usenetrc=False) # other tests.
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_capabilities(self): 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 # couple of well-known capabilities. Just sanity check that we
# got them. # got them.
def _check_caps(caps): def _check_caps(caps):
...@@ -194,6 +189,63 @@ class NetworkedNNTPTests(NetworkedNNTPTestsMixin, unittest.TestCase): ...@@ -194,6 +189,63 @@ class NetworkedNNTPTests(NetworkedNNTPTestsMixin, unittest.TestCase):
resp, caps = self.server.capabilities() resp, caps = self.server.capabilities()
_check_caps(caps) _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). # Non-networked tests using a local server (or something mocking it).
...@@ -261,7 +313,6 @@ class MockedNNTPTestsMixin: ...@@ -261,7 +313,6 @@ class MockedNNTPTestsMixin:
# Using BufferedRWPair instead of BufferedRandom ensures the file # Using BufferedRWPair instead of BufferedRandom ensures the file
# isn't seekable. # isn't seekable.
file = io.BufferedRWPair(self.sio, self.sio) file = io.BufferedRWPair(self.sio, self.sio)
kwargs.setdefault('usenetrc', False)
self.server = nntplib._NNTPBase(file, 'test.server', *args, **kwargs) self.server = nntplib._NNTPBase(file, 'test.server', *args, **kwargs)
return self.server return self.server
...@@ -1134,9 +1185,10 @@ class MiscTests(unittest.TestCase): ...@@ -1134,9 +1185,10 @@ class MiscTests(unittest.TestCase):
def test_main(): def test_main():
support.run_unittest(MiscTests, NNTPv1Tests, NNTPv2Tests, tests = [MiscTests, NNTPv1Tests, NNTPv2Tests, NetworkedNNTPTests]
NetworkedNNTPTests if _have_ssl:
) tests.append(NetworkedNNTP_SSLTests)
support.run_unittest(*tests)
if __name__ == "__main__": if __name__ == "__main__":
......
...@@ -843,6 +843,7 @@ Hector Urtubia ...@@ -843,6 +843,7 @@ Hector Urtubia
Andi Vajda Andi Vajda
Case Van Horsen Case Van Horsen
Kyle VanderBeek Kyle VanderBeek
Andrew Vant
Atul Varma Atul Varma
Dmitry Vasiliev Dmitry Vasiliev
Alexandre Vassalotti Alexandre Vassalotti
......
...@@ -60,6 +60,9 @@ Core and Builtins ...@@ -60,6 +60,9 @@ Core and Builtins
Library 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 - Issue #10335: Add tokenize.open(), detect the file encoding using
tokenize.detect_encoding() and open it in read only mode. 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