[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-113]

sftp server support!
finally check in sftp_handle (file handle abstraction), sftp_si (server
interface), and sftp_server (server implementation) -- all of which make
a roughly 90% implementation of server-side sftp.
This commit is contained in:
Robey Pointer 2004-11-22 07:27:21 +00:00
parent 611d66428e
commit a8a023a243
6 changed files with 783 additions and 2 deletions

2
README
View File

@ -204,4 +204,4 @@ v0.9 FEAROW
* multi-part auth not supported (ie, need username AND pk)
* server mode needs better documentation
* sftp server mode
* figure out if there's a way to put stdout/stderr on different channels?

0
demo_windows.py Normal file → Executable file
View File

View File

@ -67,7 +67,7 @@ __license__ = "GNU Lesser General Public License (LGPL)"
import transport, auth_transport, channel, rsakey, dsskey, message, ssh_exception, file
import sftp, sftp_client, sftp_attr, sftp_file
import sftp, sftp_client, sftp_attr, sftp_file, sftp_handle, sftp_server, sftp_si
randpool = transport.randpool
Transport = auth_transport.Transport
@ -79,8 +79,11 @@ Message = message.Message
PasswordRequiredException = ssh_exception.PasswordRequiredException
SFTP = sftp_client.SFTP
SFTPClient = sftp_client.SFTPClient
SFTPServer = sftp_server.SFTPServer
SFTPError = sftp.SFTPError
SFTPAttributes = sftp_attr.SFTPAttributes
SFTPHandle = sftp_handle.SFTPHandle
SFTPServerInterface = sftp_si.SFTPServerInterface
ServerInterface = server.ServerInterface
SubsystemHandler = server.SubsystemHandler
SecurityOptions = transport.SecurityOptions
@ -103,9 +106,12 @@ __all__ = [ 'Transport',
'SSHException',
'PasswordRequiredException',
'SFTP',
'SFTPHandle',
'SFTPClient',
'SFTPServer',
'SFTPError',
'SFTPAttributes',
'SFTPServerInterface'
'ServerInterface',
'BufferedFile',
'transport',
@ -117,8 +123,11 @@ __all__ = [ 'Transport',
'message',
'ssh_exception',
'sftp_client',
'sftp_server',
'sftp_attr',
'sftp_file',
'sftp_si',
'sftp_handle',
'server',
'file',
'util' ]

174
paramiko/sftp_handle.py Normal file
View File

@ -0,0 +1,174 @@
#!/usr/bin/python
# Copyright (C) 2003-2004 Robey Pointer <robey@lag.net>
#
# This file is part of paramiko.
#
# Paramiko is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
Abstraction of an SFTP file handle (for server mode).
"""
import os
from common import *
from sftp import *
class SFTPHandle (object):
"""
Abstract object representing a handle to an open file (or folder) on
the server. Each handle has a string representation used by the client
to refer to the underlying file.
"""
def __init__(self):
self.__name = None
# only for handles to folders:
self.__files = { }
self.__tell = None
def close(self):
"""
When a client closes a file, this method is called on the handle.
Normally you would use this method to close the underlying OS level
file object(s).
"""
pass
def read(self, offset, length):
"""
Read up to C{length} bytes from this file, starting at position
C{offset}. The offset may be a python long, since SFTP allows it
to be 64 bits.
If the end of the file has been reached, this method may return an
empty string to signify EOF, or it may also return L{SFTP_EOF}.
The default implementation checks for an attribute on C{self} named
C{readfile}, and if present, performs the read operation on the python
file-like object found there. (This is meant as a time saver for the
common case where you are wrapping a python file object.)
@param offset: position in the file to start reading from.
@type offset: int or long
@param length: number of bytes to attempt to read.
@type length: int
@return: data read from the file, or an SFTP error code.
@rtype: str
"""
if not hasattr(self, 'readfile') or (self.readfile is None):
return SFTP_OP_UNSUPPORTED
try:
if self.__tell is None:
self.__tell = self.readfile.tell()
if offset != self.__tell:
self.readfile.seek(offset)
self.__tell = offset
data = self.readfile.read(length)
except IOError, e:
self.__tell = None
return SFTPServer.convert_errno(e.errno)
self.__tell += len(data)
return data
def write(self, offset, data):
"""
Write C{data} into this file at position C{offset}. Extending the
file past its original end is expected. Unlike python's normal
C{write()} methods, this method cannot do a partial write: it must
write all of C{data} or else return an error.
The default implementation checks for an attribute on C{self} named
C{writefile}, and if present, performs the write operation on the
python file-like object found there. The attribute is named
differently from C{readfile} to make it easy to implement read-only
(or write-only) files, but if both attributes are present, they should
refer to the same file.
@param offset: position in the file to start reading from.
@type offset: int or long
@param data: data to write into the file.
@type data: str
@return: an SFTP error code like L{SFTP_OK}.
"""
if not hasattr(self, 'writefile') or (self.writefile is None):
return SFTP_OP_UNSUPPORTED
try:
if self.__tell is None:
self.__tell = self.writefile.tell()
if offset != self.__tell:
self.writefile.seek(offset)
self.__tell = offset
self.writefile.write(data)
except IOError, e:
self.__tell = None
return SFTPServer.convert_errno(e.errno)
self.__tell += len(data)
return SFTP_OK
def stat(self):
"""
Return an L{SFTPAttributes} object referring to this open file, or an
error code. This is equivalent to L{SFTPServerInterface.stat}, except
it's called on an open file instead of a path.
@return: an attributes object for the given file, or an SFTP error
code (like L{SFTP_PERMISSION_DENIED}).
@rtype: L{SFTPAttributes} I{or error code}
"""
return SFTP_OP_UNSUPPORTED
def chattr(self, attr):
"""
Change the attributes of this file. The C{attr} object will contain
only those fields provided by the client in its request, so you should
check for the presence of fields before using them.
@param attr: the attributes to change on this file.
@type attr: L{SFTPAttributes}
@return: an error code like L{SFTP_OK}.
@rtype: int
"""
return SFTP_OP_UNSUPPORTED
### internals...
def _set_files(self, files):
"""
Used by the SFTP server code to cache a directory listing. (In
the SFTP protocol, listing a directory is a multi-stage process
requiring a temporary handle.)
"""
self.__files = files
def _get_next_files(self):
"""
Used by the SFTP server code to retreive a cached directory
listing.
"""
fnlist = self.__files[:16]
self.__files = self.__files[16:]
return fnlist
def _get_name(self):
return self.__name
def _set_name(self, name):
self.__name = name
from sftp_server import SFTPServer

335
paramiko/sftp_server.py Normal file
View File

@ -0,0 +1,335 @@
#!/usr/bin/python
# Copyright (C) 2003-2004 Robey Pointer <robey@lag.net>
#
# This file is part of paramiko.
#
# Paramiko is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
Server-mode SFTP support.
"""
import os, errno
from common import *
from server import SubsystemHandler
from sftp import *
from sftp_si import *
from sftp_attr import *
class SFTPServer (BaseSFTP, SubsystemHandler):
"""
Server-side SFTP subsystem support. Since this is a L{SubsystemHandler},
it can be (and is meant to be) set as the handler for C{"sftp"} requests.
Use L{Transport.set_subsystem_handler} to activate this class.
"""
def __init__(self, channel, name, server=SFTPServerInterface, server_args=None):
"""
The constructor for SFTPServer is meant to be called from within the
L{Transport} as a subsystem handler. The C{server} and C{server_args}
parameters are passed from the original call to
L{Transport.set_subsystem_handler}.
@param channel: channel passed from the L{Transport}.
@type channel: L{Channel}
@param name: name of the requested subsystem.
@type name: str
@param server: a subclass of L{SFTPServerInterface} to use for handling
individual requests.
@type server: class
@param server_args: keyword parameters to pass to C{server} when it's
constructed.
@type server_args: dict
"""
BaseSFTP.__init__(self)
SubsystemHandler.__init__(self, channel, name)
self.ultra_debug = True
self.logger = logging.getLogger('paramiko.chan.' + channel.get_name() + '.sftp')
self.next_handle = 1
# map of handle-string to SFTPHandle for files & folders:
self.file_table = { }
self.folder_table = { }
if server_args is None:
server_args = {}
self.server = server(**server_args)
def start_subsystem(self, name, transport, channel):
self.sock = channel
self._log(DEBUG, 'Started sftp server on channel %s' % repr(channel))
self._send_server_version()
self.server.session_started()
while True:
try:
t, data = self._read_packet()
except EOFError:
self._log(DEBUG, 'EOF -- end of session')
return
except Exception, e:
self._log(DEBUG, 'Exception on channel: ' + str(e))
self._log(DEBUG, util.tb_strings())
return
msg = Message(data)
request_number = msg.get_int()
self._process(t, request_number, msg)
def finish_subsystem(self):
self.server.session_ended()
def convert_errno(e):
"""
Convert an errno value (as from an C{OSError} or C{IOError} into a
standard SFTP result code. This is a convenience function for trapping
exceptions in server code and returning an appropriate result.
@param e: an errno code, as from C{OSError.errno}.
@type e: int
@return: an SFTP error code like L{SFTP_NO_SUCH_fILE}.
@rtype: int
"""
if e == errno.EACCES:
# permission denied
return SFTP_PERMISSION_DENIED
elif e == errno.ENOENT:
# no such file
return SFTP_NO_SUCH_FILE
else:
return SFTP_FAILURE
convert_errno = staticmethod(convert_errno)
def set_file_attr(filename, attr):
"""
Change a file's attributes on the local filesystem. The contents of
C{attr} are used to change the permissions, owner, group ownership,
and/or modification & access time of the file, depending on which
attributes are present in C{attr}.
This is meant to be a handy helper function for translating SFTP file
requests into local file operations.
@param filename: name of the file to alter (should usually be an
absolute path).
@type filename: str
@param attr: attributes to change.
@type attr: L{SFTPAttributes}
"""
if attr._flags & attr.FLAG_PERMISSIONS:
os.chmod(filename, attr.st_mode)
if attr._flags & attr.FLAG_UIDGID:
os.chown(filename, attr.st_uid, attr.st_gid)
if attr._flags & attr.FLAG_AMTIME:
os.utime(filename, (attr.st_atime, attr.st_mtime))
set_file_attr = staticmethod(set_file_attr)
### internals...
def _response(self, request_number, t, *arg):
msg = Message()
msg.add_int(request_number)
for item in arg:
if type(item) is int:
msg.add_int(item)
elif type(item) is long:
msg.add_int64(item)
elif type(item) is str:
msg.add_string(item)
elif type(item) is SFTPAttributes:
item._pack(msg)
else:
raise Exception('unknown type for ' + repr(item) + ' type ' + repr(type(item)))
self._send_packet(t, str(msg))
def _send_handle_response(self, request_number, handle, folder=False):
if not issubclass(type(handle), SFTPHandle):
# must be error code
self._send_status(request_number, handle)
return
handle._set_name('hx%d' % self.next_handle)
self.next_handle += 1
if folder:
self.folder_table[handle._get_name()] = handle
else:
self.file_table[handle._get_name()] = handle
self._response(request_number, CMD_HANDLE, handle._get_name())
def _send_status(self, request_number, code, desc=None):
if desc is None:
desc = SFTP_DESC[code]
self._response(request_number, CMD_STATUS, code, desc)
def _open_folder(self, request_number, path):
resp = self.server.list_folder(path)
if issubclass(type(resp), list):
# got an actual list of filenames in the folder
folder = SFTPHandle()
folder._set_files(resp)
self._send_handle_response(request_number, folder, True)
return
# must be an error code
self._send_status(request_number, resp)
def _read_folder(self, request_number, folder):
flist = folder._get_next_files()
if len(flist) == 0:
self._send_status(request_number, SFTP_EOF)
return
msg = Message()
msg.add_int(request_number)
msg.add_int(len(flist))
for attr in flist:
msg.add_string(attr.filename)
msg.add_string(str(attr))
attr._pack(msg)
self._send_packet(CMD_NAME, str(msg))
def _convert_pflags(self, pflags):
"convert SFTP-style open() flags to python's os.open() flags"
if (pflags & SFTP_FLAG_READ) and (pflags & SFTP_FLAG_WRITE):
flags = os.O_RDWR
elif pflags & SFTP_FLAG_WRITE:
flags = os.O_WRONLY
else:
flags = os.O_RDONLY
if pflags & SFTP_FLAG_APPEND:
flags |= os.O_APPEND
if pflags & SFTP_FLAG_CREATE:
flags |= os.O_CREAT
if pflags & SFTP_FLAG_TRUNC:
flags |= os.O_TRUNC
if pflags & SFTP_FLAG_EXCL:
flags |= os.O_EXCL
return flags
def _process(self, t, request_number, msg):
self._log(DEBUG, 'Request: %s' % CMD_NAMES[t])
if t == CMD_OPEN:
path = msg.get_string()
flags = self._convert_pflags(msg.get_int())
attr = SFTPAttributes._from_msg(msg)
self._send_handle_response(request_number, self.server.open(path, flags, attr))
elif t == CMD_CLOSE:
handle = msg.get_string()
if self.folder_table.has_key(handle):
del self.folder_table[handle]
self._send_status(request_number, SFTP_OK)
return
if self.file_table.has_key(handle):
self.file_table[handle].close()
del self.file_table[handle]
self._send_status(request_number, SFTP_OK)
return
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
elif t == CMD_READ:
handle = msg.get_string()
offset = msg.get_int64()
length = msg.get_int()
if not self.file_table.has_key(handle):
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
return
data = self.file_table[handle].read(offset, length)
if type(data) is str:
if len(data) == 0:
self._send_status(request_number, SFTP_EOF)
else:
self._response(request_number, CMD_DATA, data)
else:
self._send_status(request_number, data)
elif t == CMD_WRITE:
handle = msg.get_string()
offset = msg.get_int64()
data = msg.get_string()
if not self.file_table.has_key(handle):
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
return
self._send_status(request_number, self.file_table[handle].write(offset, data))
elif t == CMD_REMOVE:
path = msg.get_string()
self._send_status(request_number, self.server.remove(path))
elif t == CMD_RENAME:
oldpath = msg.get_string()
newpath = msg.get_string()
self._send_status(request_number, self.server.rename(oldpath, newpath))
elif t == CMD_MKDIR:
path = msg.get_string()
attr = SFTPAttributes._from_msg(msg)
self._send_status(request_number, self.server.mkdir(path, attr))
elif t == CMD_RMDIR:
path = msg.get_string()
self._send_status(request_number, self.server.rmdir(path))
elif t == CMD_OPENDIR:
path = msg.get_string()
self._open_folder(request_number, path)
return
elif t == CMD_READDIR:
handle = msg.get_string()
if not self.folder_table.has_key(handle):
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
return
folder = self.folder_table[handle]
self._read_folder(request_number, folder)
elif t == CMD_STAT:
path = msg.get_string()
resp = self.server.stat(path)
if issubclass(type(resp), SFTPAttributes):
self._response(request_number, CMD_ATTRS, resp)
else:
self._send_status(request_number, resp)
elif t == CMD_LSTAT:
path = msg.get_string()
resp = self.server.lstat(path)
if issubclass(type(resp), SFTPAttributes):
self._response(request_number, CMD_ATTRS, resp)
else:
self._send_status(request_number, resp)
elif t == CMD_FSTAT:
handle = msg.get_string()
if not self.file_table.has_key(handle):
self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
return
resp = self.file_table[handle].stat()
if issubclass(type(resp), SFTPAttributes):
self._response(request_number, CMD_ATTRS, resp)
else:
self._send_status(request_number, resp)
elif t == CMD_SETSTAT:
path = msg.get_string()
attr = SFTPAttributes._from_msg(msg)
self._send_status(request_number, self.server.chattr(path, attr))
elif t == CMD_FSETSTAT:
handle = msg.get_string()
attr = SFTPAttributes._from_msg(msg)
if not self.file_table.has_key(handle):
self._response(request_number, SFTP_BAD_MESSAGE, 'Invalid handle')
return
self._send_status(request_number, self.file_table[handle].chattr(attr))
elif t == CMD_READLINK:
path = msg.get_string()
resp = self.server.readlink(path)
if type(resp) is str:
self._response(request_number, CMD_NAME, 1, resp, '', SFTPAttributes())
else:
self._send_status(request_number, resp)
elif t == CMD_REALPATH:
path = msg.get_string()
rpath = self.server.canonicalize(path)
self._response(request_number, CMD_NAME, 1, rpath, '', SFTPAttributes())
else:
self._send_status(request_number, SFTP_OP_UNSUPPORTED)
from sftp_handle import SFTPHandle

263
paramiko/sftp_si.py Normal file
View File

@ -0,0 +1,263 @@
#!/usr/bin/python
# Copyright (C) 2003-2004 Robey Pointer <robey@lag.net>
#
# This file is part of paramiko.
#
# Paramiko is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
L{SFTPServerInterface} is an interface to override for SFTP server support.
"""
import os
from common import *
from sftp import *
class SFTPServerInterface (object):
"""
This class defines an interface for controlling the behavior of paramiko
when using the L{SFTPServer} subsystem to provide an SFTP server.
Methods on this class are called from the SFTP session's thread, so you can
block as long as necessary without affecting other sessions (even other
SFTP sessions). However, raising an exception will usually cause the SFTP
session to abruptly end, so you will usually want to catch exceptions and
return an appropriate error code.
"""
def session_started(self):
"""
The SFTP server session has just started. This method is meant to be
overridden to perform any necessary setup before handling callbacks
from SFTP operations.
"""
pass
def session_ended(self):
"""
The SFTP server session has just ended, either cleanly or via an
exception. This method is meant to be overridden to perform any
necessary cleanup before this C{SFTPServerInterface} object is
destroyed.
"""
pass
def open(self, path, flags, attr):
"""
Open a file on the server and create a handle for future operations
on that file. On success, a new object subclassed from L{SFTPHandle}
should be returned. This handle will be used for future operations
on the file (read, write, etc). On failure, an error code such as
L{SFTP_PERMISSION_DENIED} should be returned.
C{flags} contains the requested mode for opening (read-only,
write-append, etc) as a bitset of flags from the C{os} module:
- C{os.O_RDONLY}
- C{os.O_WRONLY}
- C{os.O_RDWR}
- C{os.O_APPEND}
- C{os.O_CREAT}
- C{os.O_TRUNC}
- C{os.O_EXCL}
(One of C{os.O_RDONLY}, C{os.O_WRONLY}, or C{os.O_RDWR} will always
be set.)
The C{attr} object contains requested attributes of the file if it
has to be created. Some or all attribute fields may be missing if
the client didn't specify them.
@note: The SFTP protocol defines all files to be in "binary" mode.
There is no equivalent to python's "text" mode.
@param path: the requested path (relative or absolute) of the file
to be opened.
@type path: str
@param flags: flags or'd together from the C{os} module indicating the
requested mode for opening the file.
@type flags: int
@param attr: requested attributes of the file if it is newly created.
@type attr: L{SFTPAttributes}
@return: a new L{SFTPHandle} I{or error code}.
@rtype L{SFTPHandle}
"""
return SFTP_OP_UNSUPPORTED
def list_folder(self, path):
"""
Return a list of files within a given folder. The C{path} will use
posix notation (C{"/"} separates folder names) and may be an absolute
or relative path.
The list of files is expected to be a list of L{SFTPAttributes}
objects, which are similar in structure to the objects returned by
C{os.stat}. In addition, each object should have its C{filename}
field filled in, since this is important to a directory listing and
not normally present in C{os.stat} results. The method
L{SFTPAttributes.from_stat} will usually do what you want.
In case of an error, you should return one of the C{SFTP_*} error
codes, such as L{SFTP_PERMISSION_DENIED}.
@param path: the requested path (relative or absolute) to be listed.
@type path: str
@return: a list of the files in the given folder, using
L{SFTPAttributes} objects.
@rtype: list of L{SFTPAttributes} I{or error code}
@note: You should normalize the given C{path} first (see the
C{os.path} module) and check appropriate permissions before returning
the list of files. Be careful of malicious clients attempting to use
relative paths to escape restricted folders, if you're doing a direct
translation from the SFTP server path to your local filesystem.
"""
return SFTP_OP_UNSUPPORTED
def stat(self, path):
"""
Return an L{SFTPAttributes} object for a path on the server, or an
error code. If your server supports symbolic links (also known as
"aliases"), you should follow them. (L{lstat} is the corresponding
call that doesn't follow symlinks/aliases.)
@param path: the requested path (relative or absolute) to fetch
file statistics for.
@type path: str
@return: an attributes object for the given file, or an SFTP error
code (like L{SFTP_PERMISSION_DENIED}).
@rtype: L{SFTPAttributes} I{or error code}
"""
return SFTP_OP_UNSUPPORTED
def lstat(self, path):
"""
Return an L{SFTPAttributes} object for a path on the server, or an
error code. If your server supports symbolic links (also known as
"aliases"), you should I{not} follow them -- instead, you should
return data on the symlink or alias itself. (L{stat} is the
corresponding call that follows symlinks/aliases.)
@param path: the requested path (relative or absolute) to fetch
file statistics for.
@type path: str
@return: an attributes object for the given file, or an SFTP error
code (like L{SFTP_PERMISSION_DENIED}).
@rtype: L{SFTPAttributes} I{or error code}
"""
return SFTP_OP_UNSUPPORTED
def remove(self, path):
"""
Delete a file, if possible.
@param path: the requested path (relative or absolute) of the file
to delete.
@type path: str
@return: an SFTP error code like L{SFTP_OK}.
@rtype: int
"""
return SFTP_OP_UNSUPPORTED
def rename(self, oldpath, newpath):
"""
Rename (or move) a file. The SFTP specification implies that this
method can be used to move an existing file into a different folder,
and since there's no other (easy) way to move files via SFTP, it's
probably a good idea to implement "move" in this method too, even for
files that cross disk partition boundaries, if at all possible.
@note: You should return an error if a file with the same name as
C{newpath} already exists. (The rename operation should be
non-desctructive.)
@param oldpath: the requested path (relative or absolute) of the
existing file.
@type oldpath: str
@param newpath: the requested new path of the file.
@type newpath: str
@return: an SFTP error code like L{SFTP_OK}.
@rtype: int
"""
return SFTP_OP_UNSUPPORTED
def mkdir(self, path, attr):
"""
Create a new directory with the given attributes. The C{attr}
object may be considered a "hint" and ignored.
The C{attr} object will contain only those fields provided by the
client in its request, so you should use C{hasattr} to check for
the presense of fields before using them. In some cases, the C{attr}
object may be completely empty.
@param path: requested path (relative or absolute) of the new
folder.
@type path: str
@param attr: requested attributes of the new folder.
@type attr: L{SFTPAttributes}
@return: an SFTP error code like L{SFTP_OK}.
@rtype: int
"""
return SFTP_OP_UNSUPPORTED
def rmdir(self, path):
"""
Remove a directory if it exists. The C{path} should refer to an
existing, empty folder -- otherwise this method should return an
error.
@param path: requested path (relative or absolute) of the folder
to remove.
@type path: str
@return: an SFTP error code like L{SFTP_OK}.
@rtype: int
"""
return SFTP_OP_UNSUPPORTED
def chattr(self, path, attr):
"""
Change the attributes of a file. The C{attr} object will contain
only those fields provided by the client in its request, so you
should check for the presence of fields before using them.
@param path: requested path (relative or absolute) of the file to
change.
@type path: str
@param attr: requested attributes to change on the file.
@type attr: L{SFTPAttributes}
@return: an error code like L{SFTP_OK}.
@rtype: int
"""
return SFTP_OP_UNSUPPORTED
def canonicalize(self, path):
"""
Return the canonical form of a path on the server. For example,
if the server's home folder is C{/home/foo}, the path
C{"../betty"} would be canonicalized to C{"/home/betty"}. Note
the obvious security issues: if you're serving files only from a
specific folder, you probably don't want this method to reveal path
names outside that folder.
You may find the python methods in C{os.path} useful, especially
C{os.path.normpath} and C{os.path.realpath}.
The default implementation returns C{os.path.normpath('/' + path)}.
"""
if os.path.isabs(path):
return os.path.normpath(path)
else:
return os.path.normpath('/' + path)