summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Packman <gzlist@googlemail.com>2017-06-06 21:25:15 +0100
committerJeff Forcier <jeff@bitprophet.org>2021-11-28 20:24:18 -0500
commitba8c57f54d359de89920c1dbb7355ec2cbd5ed3a (patch)
treefab205a00d9aa8aaf047ccbc68f8c479d0f93f74
parent54cbd9b27c30db5ac08c18ad8aadd2075a078a22 (diff)
downloadparamiko-ba8c57f54d359de89920c1dbb7355ec2cbd5ed3a.tar.gz
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.py18
-rw-r--r--paramiko/sftp_attr.py23
-rw-r--r--tests/test_sftp.py22
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 (