bug 411099: chdir() isn't saving the cwd in a normalized way. added test.
This commit is contained in:
parent
7da1f2c4a3
commit
931f71e627
|
@ -105,7 +105,7 @@ class SFTPClient (BaseSFTP):
|
|||
chan.invoke_subsystem('sftp')
|
||||
return cls(chan)
|
||||
from_transport = classmethod(from_transport)
|
||||
|
||||
|
||||
def _log(self, level, msg, *args):
|
||||
if isinstance(msg, list):
|
||||
for m in msg:
|
||||
|
@ -116,20 +116,20 @@ class SFTPClient (BaseSFTP):
|
|||
def close(self):
|
||||
"""
|
||||
Close the SFTP session and its underlying channel.
|
||||
|
||||
|
||||
@since: 1.4
|
||||
"""
|
||||
self._log(INFO, 'sftp session closed.')
|
||||
self.sock.close()
|
||||
|
||||
|
||||
def get_channel(self):
|
||||
"""
|
||||
Return the underlying L{Channel} object for this SFTP session. This
|
||||
might be useful for doing things like setting a timeout on the channel.
|
||||
|
||||
|
||||
@return: the SSH channel
|
||||
@rtype: L{Channel}
|
||||
|
||||
|
||||
@since: 1.7.1
|
||||
"""
|
||||
return self.sock
|
||||
|
@ -148,14 +148,14 @@ class SFTPClient (BaseSFTP):
|
|||
@rtype: list of str
|
||||
"""
|
||||
return [f.filename for f in self.listdir_attr(path)]
|
||||
|
||||
|
||||
def listdir_attr(self, path='.'):
|
||||
"""
|
||||
Return a list containing L{SFTPAttributes} objects corresponding to
|
||||
files in the given C{path}. The list is in arbitrary order. It does
|
||||
not include the special entries C{'.'} and C{'..'} even if they are
|
||||
present in the folder.
|
||||
|
||||
|
||||
The returned L{SFTPAttributes} objects will each have an additional
|
||||
field: C{longname}, which may contain a formatted string of the file's
|
||||
attributes, in unix format. The content of this string will probably
|
||||
|
@ -165,7 +165,7 @@ class SFTPClient (BaseSFTP):
|
|||
@type path: str
|
||||
@return: list of attributes
|
||||
@rtype: list of L{SFTPAttributes}
|
||||
|
||||
|
||||
@since: 1.2
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
|
@ -206,7 +206,7 @@ class SFTPClient (BaseSFTP):
|
|||
existing file), C{'a+'} for reading/appending. The python C{'b'} flag
|
||||
is ignored, since SSH treats all files as binary. The C{'U'} flag is
|
||||
supported in a compatible way.
|
||||
|
||||
|
||||
Since 1.5.2, an C{'x'} flag indicates that the operation should only
|
||||
succeed if the file was created and did not previously exist. This has
|
||||
no direct mapping to python's file flags, but is commonly known as the
|
||||
|
@ -276,7 +276,7 @@ class SFTPClient (BaseSFTP):
|
|||
@type oldpath: str
|
||||
@param newpath: new name for the file or folder
|
||||
@type newpath: str
|
||||
|
||||
|
||||
@raise IOError: if C{newpath} is a folder, or something else goes
|
||||
wrong
|
||||
"""
|
||||
|
@ -389,7 +389,7 @@ class SFTPClient (BaseSFTP):
|
|||
attr = SFTPAttributes()
|
||||
attr.st_mode = mode
|
||||
self._request(CMD_SETSTAT, path, attr)
|
||||
|
||||
|
||||
def chown(self, path, uid, gid):
|
||||
"""
|
||||
Change the owner (C{uid}) and group (C{gid}) of a file. As with
|
||||
|
@ -438,7 +438,7 @@ class SFTPClient (BaseSFTP):
|
|||
Change the size of the file specified by C{path}. This usually extends
|
||||
or shrinks the size of the file, just like the C{truncate()} method on
|
||||
python file objects.
|
||||
|
||||
|
||||
@param path: path of the file to modify
|
||||
@type path: str
|
||||
@param size: the new size of the file
|
||||
|
@ -484,7 +484,7 @@ class SFTPClient (BaseSFTP):
|
|||
@type path: str
|
||||
@return: normalized form of the given path
|
||||
@rtype: str
|
||||
|
||||
|
||||
@raise IOError: if the path can't be resolved on the server
|
||||
"""
|
||||
path = self._adjust_cwd(path)
|
||||
|
@ -496,47 +496,51 @@ class SFTPClient (BaseSFTP):
|
|||
if count != 1:
|
||||
raise SFTPError('Realpath returned %d results' % count)
|
||||
return _to_unicode(msg.get_string())
|
||||
|
||||
|
||||
def chdir(self, path):
|
||||
"""
|
||||
Change the "current directory" of this SFTP session. Since SFTP
|
||||
doesn't really have the concept of a current working directory, this
|
||||
is emulated by paramiko. Once you use this method to set a working
|
||||
directory, all operations on this SFTPClient object will be relative
|
||||
to that path.
|
||||
|
||||
to that path. You can pass in C{None} to stop using a current working
|
||||
directory.
|
||||
|
||||
@param path: new current working directory
|
||||
@type path: str
|
||||
|
||||
|
||||
@raise IOError: if the requested path doesn't exist on the server
|
||||
|
||||
|
||||
@since: 1.4
|
||||
"""
|
||||
if path is None:
|
||||
self._cwd = None
|
||||
return
|
||||
if not stat.S_ISDIR(self.stat(path).st_mode):
|
||||
raise SFTPError(errno.ENOTDIR, "%s: %s" % (os.strerror(errno.ENOTDIR), path))
|
||||
self._cwd = self.normalize(path)
|
||||
|
||||
self._cwd = self.normalize(path).encode('utf-8')
|
||||
|
||||
def getcwd(self):
|
||||
"""
|
||||
Return the "current working directory" for this SFTP session, as
|
||||
emulated by paramiko. If no directory has been set with L{chdir},
|
||||
this method will return C{None}.
|
||||
|
||||
|
||||
@return: the current working directory on the server, or C{None}
|
||||
@rtype: str
|
||||
|
||||
|
||||
@since: 1.4
|
||||
"""
|
||||
return self._cwd
|
||||
|
||||
|
||||
def put(self, localpath, remotepath, callback=None):
|
||||
"""
|
||||
Copy a local file (C{localpath}) to the SFTP server as C{remotepath}.
|
||||
Any exception raised by operations will be passed through. This
|
||||
method is primarily provided as a convenience.
|
||||
|
||||
|
||||
The SFTP operations use pipelining for speed.
|
||||
|
||||
|
||||
@param localpath: the local file to copy
|
||||
@type localpath: str
|
||||
@param remotepath: the destination path on the SFTP server
|
||||
|
@ -548,7 +552,7 @@ class SFTPClient (BaseSFTP):
|
|||
@return: an object containing attributes about the given file
|
||||
(since 1.7.4)
|
||||
@rtype: SFTPAttributes
|
||||
|
||||
|
||||
@since: 1.4
|
||||
"""
|
||||
file_size = os.stat(localpath).st_size
|
||||
|
@ -574,13 +578,13 @@ class SFTPClient (BaseSFTP):
|
|||
if s.st_size != size:
|
||||
raise IOError('size mismatch in put! %d != %d' % (s.st_size, size))
|
||||
return s
|
||||
|
||||
|
||||
def get(self, remotepath, localpath, callback=None):
|
||||
"""
|
||||
Copy a remote file (C{remotepath}) from the SFTP server to the local
|
||||
host as C{localpath}. Any exception raised by operations will be
|
||||
passed through. This method is primarily provided as a convenience.
|
||||
|
||||
|
||||
@param remotepath: the remote file to copy
|
||||
@type remotepath: str
|
||||
@param localpath: the destination path on the local host
|
||||
|
@ -589,7 +593,7 @@ class SFTPClient (BaseSFTP):
|
|||
transferred so far and the total bytes to be transferred
|
||||
(since 1.7.4)
|
||||
@type callback: function(int, int)
|
||||
|
||||
|
||||
@since: 1.4
|
||||
"""
|
||||
fr = self.file(remotepath, 'rb')
|
||||
|
@ -622,7 +626,7 @@ class SFTPClient (BaseSFTP):
|
|||
def _request(self, t, *arg):
|
||||
num = self._async_request(type(None), t, *arg)
|
||||
return self._read_response(num)
|
||||
|
||||
|
||||
def _async_request(self, fileobj, t, *arg):
|
||||
# this method may be called from other threads (prefetch)
|
||||
self._lock.acquire()
|
||||
|
@ -699,7 +703,7 @@ class SFTPClient (BaseSFTP):
|
|||
raise IOError(errno.EACCES, text)
|
||||
else:
|
||||
raise IOError(text)
|
||||
|
||||
|
||||
def _adjust_cwd(self, path):
|
||||
"""
|
||||
Return an adjusted path if we're emulating a "current working
|
||||
|
|
|
@ -80,7 +80,7 @@ class SFTPTest (unittest.TestCase):
|
|||
|
||||
def init(hostname, username, keyfile, passwd):
|
||||
global sftp, tc
|
||||
|
||||
|
||||
t = paramiko.Transport(hostname)
|
||||
tc = t
|
||||
try:
|
||||
|
@ -136,7 +136,7 @@ class SFTPTest (unittest.TestCase):
|
|||
global g_big_file_test
|
||||
g_big_file_test = onoff
|
||||
set_big_file_test = staticmethod(set_big_file_test)
|
||||
|
||||
|
||||
def setUp(self):
|
||||
global FOLDER
|
||||
for i in xrange(1000):
|
||||
|
@ -206,7 +206,7 @@ class SFTPTest (unittest.TestCase):
|
|||
f.close()
|
||||
finally:
|
||||
sftp.remove(FOLDER + '/append.txt')
|
||||
|
||||
|
||||
def test_5_rename(self):
|
||||
"""
|
||||
verify that renaming a file works.
|
||||
|
@ -309,7 +309,7 @@ class SFTPTest (unittest.TestCase):
|
|||
self.assertEqual(stat.st_atime, atime)
|
||||
|
||||
# can't really test chown, since we'd have to know a valid uid.
|
||||
|
||||
|
||||
sftp.truncate(FOLDER + '/special', 512)
|
||||
stat = sftp.stat(FOLDER + '/special')
|
||||
self.assertEqual(stat.st_size, 512)
|
||||
|
@ -325,7 +325,7 @@ class SFTPTest (unittest.TestCase):
|
|||
try:
|
||||
f.write('x' * 1024)
|
||||
f.close()
|
||||
|
||||
|
||||
f = sftp.open(FOLDER + '/special', 'r+')
|
||||
stat = f.stat()
|
||||
f.chmod((stat.st_mode & ~0777) | 0600)
|
||||
|
@ -348,16 +348,16 @@ class SFTPTest (unittest.TestCase):
|
|||
self.assertEqual(stat.st_mtime, mtime)
|
||||
if sys.platform not in ('win32', 'cygwin'):
|
||||
self.assertEqual(stat.st_atime, atime)
|
||||
|
||||
|
||||
# can't really test chown, since we'd have to know a valid uid.
|
||||
|
||||
|
||||
f.truncate(512)
|
||||
stat = f.stat()
|
||||
self.assertEqual(stat.st_size, 512)
|
||||
f.close()
|
||||
finally:
|
||||
sftp.remove(FOLDER + '/special')
|
||||
|
||||
|
||||
def test_A_readline_seek(self):
|
||||
"""
|
||||
create a text file and write a bunch of text into it. then count the lines
|
||||
|
@ -510,7 +510,7 @@ class SFTPTest (unittest.TestCase):
|
|||
self.assert_(False, 'no exception removing nonexistent subfolder')
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
|
||||
def test_G_chdir(self):
|
||||
"""
|
||||
verify that chdir/getcwd work.
|
||||
|
@ -524,7 +524,7 @@ class SFTPTest (unittest.TestCase):
|
|||
sftp.mkdir('beta')
|
||||
self.assertEquals(root + FOLDER + '/alpha', sftp.getcwd())
|
||||
self.assertEquals(['beta'], sftp.listdir('.'))
|
||||
|
||||
|
||||
sftp.chdir('beta')
|
||||
f = sftp.open('fish', 'w')
|
||||
f.write('hello\n')
|
||||
|
@ -554,7 +554,7 @@ class SFTPTest (unittest.TestCase):
|
|||
"""
|
||||
import os, warnings
|
||||
warnings.filterwarnings('ignore', 'tempnam.*')
|
||||
|
||||
|
||||
localname = os.tempnam()
|
||||
text = 'All I wanted was a plastic bunny rabbit.\n'
|
||||
f = open(localname, 'wb')
|
||||
|
@ -564,22 +564,22 @@ class SFTPTest (unittest.TestCase):
|
|||
def progress_callback(x, y):
|
||||
saved_progress.append((x, y))
|
||||
sftp.put(localname, FOLDER + '/bunny.txt', progress_callback)
|
||||
|
||||
|
||||
f = sftp.open(FOLDER + '/bunny.txt', 'r')
|
||||
self.assertEquals(text, f.read(128))
|
||||
f.close()
|
||||
self.assertEquals((41, 41), saved_progress[-1])
|
||||
|
||||
|
||||
os.unlink(localname)
|
||||
localname = os.tempnam()
|
||||
saved_progress = []
|
||||
sftp.get(FOLDER + '/bunny.txt', localname, progress_callback)
|
||||
|
||||
|
||||
f = open(localname, 'rb')
|
||||
self.assertEquals(text, f.read(128))
|
||||
f.close()
|
||||
self.assertEquals((41, 41), saved_progress[-1])
|
||||
|
||||
|
||||
os.unlink(localname)
|
||||
sftp.unlink(FOLDER + '/bunny.txt')
|
||||
|
||||
|
@ -592,7 +592,7 @@ class SFTPTest (unittest.TestCase):
|
|||
f = sftp.open(FOLDER + '/kitty.txt', 'w')
|
||||
f.write('here kitty kitty' * 64)
|
||||
f.close()
|
||||
|
||||
|
||||
try:
|
||||
f = sftp.open(FOLDER + '/kitty.txt', 'r')
|
||||
sum = f.check('sha1')
|
||||
|
@ -612,7 +612,7 @@ class SFTPTest (unittest.TestCase):
|
|||
"""
|
||||
f = sftp.open(FOLDER + '/unusual.txt', 'wx')
|
||||
f.close()
|
||||
|
||||
|
||||
try:
|
||||
try:
|
||||
f = sftp.open(FOLDER + '/unusual.txt', 'wx')
|
||||
|
@ -621,7 +621,7 @@ class SFTPTest (unittest.TestCase):
|
|||
pass
|
||||
finally:
|
||||
sftp.unlink(FOLDER + '/unusual.txt')
|
||||
|
||||
|
||||
def test_K_utf8(self):
|
||||
"""
|
||||
verify that unicode strings are encoded into utf8 correctly.
|
||||
|
@ -629,7 +629,7 @@ class SFTPTest (unittest.TestCase):
|
|||
f = sftp.open(FOLDER + '/something', 'w')
|
||||
f.write('okay')
|
||||
f.close()
|
||||
|
||||
|
||||
try:
|
||||
sftp.rename(FOLDER + '/something', FOLDER + u'/\u00fcnic\u00f8de')
|
||||
sftp.open(FOLDER + '/\xc3\xbcnic\xc3\xb8\x64\x65', 'r')
|
||||
|
@ -637,7 +637,19 @@ class SFTPTest (unittest.TestCase):
|
|||
self.fail('exception ' + e)
|
||||
sftp.unlink(FOLDER + '/\xc3\xbcnic\xc3\xb8\x64\x65')
|
||||
|
||||
def test_L_bad_readv(self):
|
||||
def test_L_utf8_chdir(self):
|
||||
sftp.mkdir(FOLDER + u'\u00fcnic\u00f8de')
|
||||
try:
|
||||
sftp.chdir(FOLDER + u'\u00fcnic\u00f8de')
|
||||
f = sftp.open('something', 'w')
|
||||
f.write('okay')
|
||||
f.close()
|
||||
sftp.unlink('something')
|
||||
finally:
|
||||
sftp.chdir(None)
|
||||
sftp.rmdir(FOLDER + u'\u00fcnic\u00f8de')
|
||||
|
||||
def test_M_bad_readv(self):
|
||||
"""
|
||||
verify that readv at the end of the file doesn't essplode.
|
||||
"""
|
||||
|
@ -647,7 +659,7 @@ class SFTPTest (unittest.TestCase):
|
|||
f = sftp.open(FOLDER + '/zero', 'r')
|
||||
data = f.readv([(0, 12)])
|
||||
f.close()
|
||||
|
||||
|
||||
f = sftp.open(FOLDER + '/zero', 'r')
|
||||
f.prefetch()
|
||||
data = f.read(100)
|
||||
|
@ -658,7 +670,7 @@ class SFTPTest (unittest.TestCase):
|
|||
def XXX_test_M_seek_append(self):
|
||||
"""
|
||||
verify that seek does't affect writes during append.
|
||||
|
||||
|
||||
does not work except through paramiko. :( openssh fails.
|
||||
"""
|
||||
f = sftp.open(FOLDER + '/append.txt', 'a')
|
||||
|
|
Loading…
Reference in New Issue