diff -r 1756beed417c Lib/nntplib.py --- a/Lib/nntplib.py Sat Dec 17 09:19:11 2016 +0100 +++ b/Lib/nntplib.py Sat Dec 17 16:04:33 2016 +0100 @@ -85,11 +85,9 @@ "decode_header", ] -# maximal line length when calling readline(). This is to prevent -# reading arbitrary length lines. RFC 3977 limits NNTP line length to -# 512 characters, including CRLF. We have selected 2048 just to be on -# the safe side. -_MAXLINE = 2048 +# A response from the server exceeding _MAXBYTES is considered a malicious Dos +# attack. +_MAXBYTES = 100 * 1024 * 1024 # Exceptions raised when an error or invalid response is received @@ -413,6 +411,8 @@ def _putline(self, line): """Internal: send one line to the server, appending CRLF. The `line` must be a bytes-like object.""" + if not hasattr(self, 'file'): + raise NNTPPermanentError('The session is closed.') line = line + _CRLF if self.debugging > 1: print('*put*', repr(line)) self.file.write(line) @@ -429,9 +429,13 @@ """Internal: return one line from the server, stripping _CRLF. Raise EOFError if the connection is closed. Returns a bytes object.""" - line = self.file.readline(_MAXLINE +1) - if len(line) > _MAXLINE: - raise NNTPDataError('line too long') + if not hasattr(self, 'file'): + raise NNTPPermanentError('The session is closed.') + line = self.file.readline(_MAXBYTES + 1) + if len(line) > _MAXBYTES: + self._close() + raise NNTPPermanentError('Too many bytes received from the' + ' server, closing the session.') if self.debugging > 1: print('*get*', repr(line)) if not line: raise EOFError @@ -477,26 +481,26 @@ if resp[:3] not in _LONGRESP: raise NNTPReplyError(resp) + count = len(resp) lines = [] - if file is not None: - # XXX lines = None instead? - terminators = (b'.' + _CRLF, b'.\n') - while 1: - line = self._getline(False) - if line in terminators: - break - if line.startswith(b'..'): - line = line[1:] + file_is_None = (file is None) + terminators = (b'.', ) if file_is_None else (b'.' + _CRLF, b'.\n') + while 1: + line = self._getline(file_is_None) + count += len(line) + if count > _MAXBYTES: + self._close() + raise NNTPPermanentError('Too many bytes received from' + ' the server, closing the session.') + if line in terminators: + break + if line.startswith(b'..'): + line = line[1:] + if file_is_None: + lines.append(line) + else: file.write(line) - else: - terminator = b'.' - while 1: - line = self._getline() - if line == terminator: - break - if line.startswith(b'..'): - line = line[1:] - lines.append(line) + finally: # If this method created the file, then it must close it if openedFile: @@ -888,6 +892,8 @@ return resp, _parse_datetime(date, None) def _post(self, command, f): + if not hasattr(self, 'file'): + raise NNTPPermanentError('The session is closed.') resp = self._shortcmd(command) # Raises a specific exception if posting is not allowed if not resp.startswith('3'): @@ -933,9 +939,12 @@ - resp: server response if successful""" try: resp = self._shortcmd('QUIT') + except NNTPPermanentError: + pass + else: + return resp finally: self._close() - return resp def login(self, user=None, password=None, usenetrc=True): if self.authenticated: diff -r 1756beed417c Lib/test/test_nntplib.py --- a/Lib/test/test_nntplib.py Sat Dec 17 09:19:11 2016 +0100 +++ b/Lib/test/test_nntplib.py Sat Dec 17 16:04:33 2016 +0100 @@ -132,8 +132,6 @@ self.assertLessEqual(art_num, last) self._check_art_dict(art_dict) - @unittest.skipIf(True, 'temporarily skipped until a permanent solution' - ' is found for issue #28971') def test_over(self): resp, count, first, last, name = self.server.group(self.GROUP_NAME) start = last - 10 @@ -1183,8 +1181,13 @@ def test_too_long_lines(self): dt = datetime.datetime(2010, 1, 1, 9, 0, 0) - self.assertRaises(nntplib.NNTPDataError, + oldv = nntplib._MAXBYTES + nntplib._MAXBYTES = 2048 + try: + self.assertRaises(nntplib.NNTPPermanentError, self.server.newnews, "comp.lang.python", dt) + finally: + nntplib._MAXBYTES = oldv class NNTPv1Tests(NNTPv1v2TestsMixin, MockedNNTPTestsMixin, unittest.TestCase):