diff options
-rw-r--r-- | paramiko/file.py | 16 | ||||
-rw-r--r-- | sites/www/changelog.rst | 4 | ||||
-rwxr-xr-x | tests/test_file.py | 6 |
3 files changed, 21 insertions, 5 deletions
diff --git a/paramiko/file.py b/paramiko/file.py index 311e1982..e3b0a16a 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -206,6 +206,7 @@ class BufferedFile (ClosingContextManager): if not (self._flags & self.FLAG_READ): raise IOError('File not open for reading') line = self._rbuffer + truncated = False while True: if self._at_trailing_cr and (self._flags & self.FLAG_UNIVERSAL_NEWLINE) and (len(line) > 0): # edge case: the newline may be '\r\n' and we may have read @@ -220,11 +221,11 @@ class BufferedFile (ClosingContextManager): # enough. if (size is not None) and (size >= 0): if len(line) >= size: - # truncate line and return + # truncate line self._rbuffer = line[size:] line = line[:size] - self._pos += len(line) - return line if self._flags & self.FLAG_BINARY else u(line) + truncated = True + break n = size - len(line) else: n = self._bufsize @@ -246,10 +247,17 @@ class BufferedFile (ClosingContextManager): rpos = line.find(cr_byte) if (rpos >= 0) and (rpos < pos or pos < 0): pos = rpos + if pos == -1: + # we couldn't find a newline in the truncated string, return it + self._pos += len(line) + return line if self._flags & self.FLAG_BINARY else u(line) xpos = pos + 1 if (line[pos] == cr_byte_value) and (xpos < len(line)) and (line[xpos] == linefeed_byte_value): xpos += 1 - self._rbuffer = line[xpos:] + # if the string was truncated, _rbuffer needs to have the string after + # the newline character plus the truncated part of the line we stored + # earlier in _rbuffer + self._rbuffer = line[xpos:] + self._rbuffer if truncated else line[xpos:] lf = line[pos:xpos] line = line[:pos] + linefeed_byte if (len(self._rbuffer) == 0) and (lf == cr_byte): diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 9c2e2a0f..9603e6d5 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,10 @@ Changelog ========= +* :bug:`428` Fix an issue in `~paramiko.file.BufferedFile` (primarily used in + the SFTP modules) concerning incorrect behavior by + `~paramiko.file.BufferedFile.readlines` on files whose size exceeds the + buffer size. Thanks to ``@achapp`` for catch & patch. * :bug:`415` Fix ``ssh_config`` parsing to correctly interpret ``ProxyCommand none`` as the lack of a proxy command, instead of as a literal command string of ``"none"``. Thanks to Richard Spiers for the catch & Sean Johnson for the diff --git a/tests/test_file.py b/tests/test_file.py index 22a34aca..a6ff69e9 100755 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -70,13 +70,17 @@ class BufferedFileTest (unittest.TestCase): def test_2_readline(self): f = LoopbackFile('r+U') - f.write(b'First line.\nSecond line.\r\nThird line.\nFinal line non-terminated.') + f.write(b'First line.\nSecond line.\r\nThird line.\n' + + b'Fourth line.\nFinal line non-terminated.') + self.assertEqual(f.readline(), 'First line.\n') # universal newline mode should convert this linefeed: self.assertEqual(f.readline(), 'Second line.\n') # truncated line: self.assertEqual(f.readline(7), 'Third l') self.assertEqual(f.readline(), 'ine.\n') + # newline should be detected and only the fourth line returned + self.assertEqual(f.readline(39), 'Fourth line.\n') self.assertEqual(f.readline(), 'Final line non-terminated.') self.assertEqual(f.readline(), '') f.close() |