mirror of https://github.com/pkg/sftp.git
request-server: Introduce ReadlinkFileLister
ReadlinkFileLister with its Readlink method allows returning paths without misusing the os.FileInfo interface, whose Name() method should only return the base name of a file. By implementing ReadlinkFileLister, it is possible to easily return symlinks of any kind (absolute, relative, multiple directory levels)
This commit is contained in:
parent
eb2fffbb98
commit
9183e7fd79
|
@ -391,21 +391,6 @@ func (fs *root) Filelist(r *Request) (ListerAt, error) {
|
|||
return nil, err
|
||||
}
|
||||
return listerat{file}, nil
|
||||
|
||||
case "Readlink":
|
||||
symlink, err := fs.readlink(r.Filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// SFTP-v2: The server will respond with a SSH_FXP_NAME packet containing only
|
||||
// one name and a dummy attributes value.
|
||||
return listerat{
|
||||
&memFile{
|
||||
name: symlink,
|
||||
err: os.ErrNotExist, // prevent accidental use as a reader/writer.
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("unsupported")
|
||||
|
@ -434,7 +419,7 @@ func (fs *root) readdir(pathname string) ([]os.FileInfo, error) {
|
|||
return files, nil
|
||||
}
|
||||
|
||||
func (fs *root) readlink(pathname string) (string, error) {
|
||||
func (fs *root) Readlink(pathname string) (string, error) {
|
||||
file, err := fs.lfetch(pathname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
|
@ -74,6 +74,11 @@ type StatVFSFileCmder interface {
|
|||
// FileLister should return an object that fulfils the ListerAt interface
|
||||
// Note in cases of an error, the error text will be sent to the client.
|
||||
// Called for Methods: List, Stat, Readlink
|
||||
//
|
||||
// Since Filelist returns an os.FileInfo, this can make it non-ideal for impelmenting Readlink.
|
||||
// This is because the Name receiver method defined by that interface defines that it should only return the base name.
|
||||
// However, Readlink is required to be capable of returning essentially any arbitrary valid path relative or absolute.
|
||||
// In order to implement this more expressive requirement, implement [ReadlinkFileLister] which will then be used instead.
|
||||
type FileLister interface {
|
||||
Filelist(*Request) (ListerAt, error)
|
||||
}
|
||||
|
@ -94,7 +99,7 @@ type LstatFileLister interface {
|
|||
//
|
||||
// Up to v1.13.5 the signature for the RealPath method was:
|
||||
//
|
||||
// RealPath(string) string
|
||||
// # RealPath(string) string
|
||||
//
|
||||
// we have added a legacyRealPathFileLister that implements the old method
|
||||
// to ensure that your code does not break.
|
||||
|
@ -104,6 +109,14 @@ type RealPathFileLister interface {
|
|||
RealPath(string) (string, error)
|
||||
}
|
||||
|
||||
// ReadlinkFileLister is a FileLister that implements the Readlink method.
|
||||
// By implementing the Readlink method, it is possible to return any arbitrary valid path relative or absolute.
|
||||
// This allows giving a better response than via the default FileLister (which is limited to os.FileInfo, whose Name method should only return the base name of a file)
|
||||
type ReadlinkFileLister interface {
|
||||
FileLister
|
||||
Readlink(string) (string, error)
|
||||
}
|
||||
|
||||
// This interface is here for backward compatibility only
|
||||
type legacyRealPathFileLister interface {
|
||||
FileLister
|
||||
|
|
|
@ -607,6 +607,10 @@ func TestRequestSymlink(t *testing.T) {
|
|||
for _, s := range symlinks {
|
||||
err := p.cli.Symlink(s.target, s.name)
|
||||
require.NoError(t, err, "Creating symlink %q with target %q failed", s.name, s.target)
|
||||
|
||||
rl, err := p.cli.ReadLink(s.name)
|
||||
require.NoError(t, err, "ReadLink(%q) failed", s.name)
|
||||
require.Equal(t, s.target, rl, "Unexpected result when reading symlink %q", s.name)
|
||||
}
|
||||
|
||||
// test fetching via symlink
|
||||
|
|
24
request.go
24
request.go
|
@ -295,7 +295,12 @@ func (r *Request) call(handlers Handlers, pkt requestPacket, alloc *allocator, o
|
|||
return filecmd(handlers.FileCmd, r, pkt)
|
||||
case "List":
|
||||
return filelist(handlers.FileList, r, pkt)
|
||||
case "Stat", "Lstat", "Readlink":
|
||||
case "Stat", "Lstat":
|
||||
return filestat(handlers.FileList, r, pkt)
|
||||
case "Readlink":
|
||||
if readlinkFileLister, ok := handlers.FileList.(ReadlinkFileLister); ok {
|
||||
return readlink(readlinkFileLister, r, pkt)
|
||||
}
|
||||
return filestat(handlers.FileList, r, pkt)
|
||||
default:
|
||||
return statusFromError(pkt.id(), fmt.Errorf("unexpected method: %s", r.Method))
|
||||
|
@ -599,6 +604,23 @@ func filestat(h FileLister, r *Request, pkt requestPacket) responsePacket {
|
|||
}
|
||||
}
|
||||
|
||||
func readlink(readlinkFileLister ReadlinkFileLister, r *Request, pkt requestPacket) responsePacket {
|
||||
resolved, err := readlinkFileLister.Readlink(r.Filepath)
|
||||
if err != nil {
|
||||
return statusFromError(pkt.id(), err)
|
||||
}
|
||||
return &sshFxpNamePacket{
|
||||
ID: pkt.id(),
|
||||
NameAttrs: []*sshFxpNameAttr{
|
||||
{
|
||||
Name: resolved,
|
||||
LongName: resolved,
|
||||
Attrs: emptyFileStat,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// init attributes of request object from packet data
|
||||
func requestMethod(p requestPacket) (method string) {
|
||||
switch p.(type) {
|
||||
|
|
Loading…
Reference in New Issue