summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--paramiko/file.py16
-rw-r--r--sites/www/changelog.rst4
-rwxr-xr-xtests/test_file.py6
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()