diff options
author | Martin Packman <gzlist@googlemail.com> | 2017-06-06 21:25:15 +0100 |
---|---|---|
committer | Jeff Forcier <jeff@bitprophet.org> | 2021-11-28 20:24:18 -0500 |
commit | ba8c57f54d359de89920c1dbb7355ec2cbd5ed3a (patch) | |
tree | fab205a00d9aa8aaf047ccbc68f8c479d0f93f74 | |
parent | 54cbd9b27c30db5ac08c18ad8aadd2075a078a22 (diff) |
Fix failure in listdir when server uses a locale
Fixes #985
SFTPAttributes uses the locale-aware %b directive for the
abbreviated month name with time.strftime, but was not
decoding the result on Python 2.7.
Add a strftime alias in py3compat that will always return
unicode, and resolve the mixing of bytes and text in
SFTPAttributes methods.
Add a test at the listdir level, and a more specific test
for the SFTPAttributes asbytes method.
-rw-r--r-- | paramiko/py3compat.py | 18 | ||||
-rw-r--r-- | paramiko/sftp_attr.py | 23 | ||||
-rw-r--r-- | tests/test_sftp.py | 22 |
3 files changed, 51 insertions, 12 deletions
diff --git a/paramiko/py3compat.py b/paramiko/py3compat.py index 0f80e19f..0330abac 100644 --- a/paramiko/py3compat.py +++ b/paramiko/py3compat.py @@ -1,5 +1,6 @@ -import sys import base64 +import sys +import time __all__ = [ "BytesIO", @@ -29,6 +30,9 @@ __all__ = [ PY2 = sys.version_info[0] < 3 if PY2: + import __builtin__ as builtins + import locale + string_types = basestring # NOQA text_type = unicode # NOQA bytes_types = str @@ -39,7 +43,10 @@ if PY2: decodebytes = base64.decodestring encodebytes = base64.encodestring - import __builtin__ as builtins + def bytestring(s): # NOQA + if isinstance(s, unicode): # NOQA + return s.encode('utf-8') + return s byte_ord = ord # NOQA byte_chr = chr # NOQA @@ -100,6 +107,11 @@ if PY2: # 64-bit MAXSIZE = int((1 << 63) - 1) # NOQA del X + + def strftime(format, t): + """Same as time.strftime but returns unicode.""" + _, encoding = locale.getlocale(locale.LC_TIME) + return time.strftime(format, t).decode(encoding or 'ascii') else: import collections import struct @@ -167,3 +179,5 @@ else: next = next MAXSIZE = sys.maxsize # NOQA + + strftime = time.strftime # NOQA diff --git a/paramiko/sftp_attr.py b/paramiko/sftp_attr.py index f16ac746..1ad16349 100644 --- a/paramiko/sftp_attr.py +++ b/paramiko/sftp_attr.py @@ -19,7 +19,7 @@ import stat import time from paramiko.common import x80000000, o700, o70, xffffffff -from paramiko.py3compat import long, b +from paramiko.py3compat import long, PY2, strftime class SFTPAttributes(object): @@ -169,7 +169,7 @@ class SFTPAttributes(object): out += "-xSs"[suid + (n & 1)] return out - def __str__(self): + def _as_text(self): """create a unix-style long description of the file (like ls -l)""" if self.st_mode is not None: kind = stat.S_IFMT(self.st_mode) @@ -205,16 +205,13 @@ class SFTPAttributes(object): # shouldn't really happen datestr = "(unknown date)" else: + time_tuple = time.localtime(self.st_mtime) if abs(time.time() - self.st_mtime) > 15552000: # (15552000 = 6 months) - datestr = time.strftime( - "%d %b %Y", time.localtime(self.st_mtime) - ) + datestr = strftime('%d %b %Y', time_tuple) else: - datestr = time.strftime( - "%d %b %H:%M", time.localtime(self.st_mtime) - ) - filename = getattr(self, "filename", "?") + datestr = strftime('%d %b %H:%M', time_tuple) + filename = getattr(self, 'filename', '?') # not all servers support uid/gid uid = self.st_uid @@ -240,4 +237,10 @@ class SFTPAttributes(object): ) def asbytes(self): - return b(str(self)) + return self._as_text().encode('utf-8') + + if PY2: + __unicode__ = _as_text + __str__ = asbytes + else: + __str__ = _as_text diff --git a/tests/test_sftp.py b/tests/test_sftp.py index a98a46c6..84c5252b 100644 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -34,6 +34,11 @@ import pytest from paramiko.py3compat import PY2, b, u, StringIO from paramiko.common import o777, o600, o666, o644 +from tests import requireNonAsciiLocale, skipUnlessBuiltin +from tests.stub_sftp import StubServer, StubSFTPServer +from tests.loop import LoopSocket +from tests.util import test_path +import paramiko.util from paramiko.sftp_attr import SFTPAttributes from .util import needs_builtin @@ -270,6 +275,16 @@ class TestSFTP(object): sftp.remove(sftp.FOLDER + "/fish.txt") sftp.remove(sftp.FOLDER + "/tertiary.py") + @requireNonAsciiLocale() + def test_listdir_in_locale(self): + """Test listdir under a locale that uses non-ascii text.""" + sftp.open(FOLDER + '/canard.txt', 'w').close() + try: + folder_contents = sftp.listdir(FOLDER) + self.assertEqual(['canard.txt'], folder_contents) + finally: + sftp.remove(FOLDER + '/canard.txt') + def test_setstat(self, sftp): """ verify that the setstat functions (chown, chmod, utime, truncate) work. @@ -781,6 +796,13 @@ class TestSFTP(object): finally: sftp.remove("%s/nonutf8data" % sftp.FOLDER) + @requireNonAsciiLocale('LC_TIME') + def test_sftp_attributes_locale_time(self): + """Test SFTPAttributes under a locale with non-ascii time strings.""" + some_stat = os.stat(sftp.FOLDER) + sftp_attributes = SFTPAttributes.from_stat(some_stat, u('a_directory')) + self.assertTrue(b'a_directory' in sftp_attributes.asbytes()) + def test_sftp_attributes_empty_str(self, sftp): sftp_attributes = SFTPAttributes() assert ( |