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 nil, err
|
||||||
}
|
}
|
||||||
return listerat{file}, nil
|
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")
|
return nil, errors.New("unsupported")
|
||||||
|
@ -434,7 +419,7 @@ func (fs *root) readdir(pathname string) ([]os.FileInfo, error) {
|
||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *root) readlink(pathname string) (string, error) {
|
func (fs *root) Readlink(pathname string) (string, error) {
|
||||||
file, err := fs.lfetch(pathname)
|
file, err := fs.lfetch(pathname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
@ -74,6 +74,11 @@ type StatVFSFileCmder interface {
|
||||||
// FileLister should return an object that fulfils the ListerAt 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.
|
// Note in cases of an error, the error text will be sent to the client.
|
||||||
// Called for Methods: List, Stat, Readlink
|
// 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 {
|
type FileLister interface {
|
||||||
Filelist(*Request) (ListerAt, error)
|
Filelist(*Request) (ListerAt, error)
|
||||||
}
|
}
|
||||||
|
@ -94,7 +99,7 @@ type LstatFileLister interface {
|
||||||
//
|
//
|
||||||
// Up to v1.13.5 the signature for the RealPath method was:
|
// 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
|
// we have added a legacyRealPathFileLister that implements the old method
|
||||||
// to ensure that your code does not break.
|
// to ensure that your code does not break.
|
||||||
|
@ -104,6 +109,14 @@ type RealPathFileLister interface {
|
||||||
RealPath(string) (string, error)
|
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
|
// This interface is here for backward compatibility only
|
||||||
type legacyRealPathFileLister interface {
|
type legacyRealPathFileLister interface {
|
||||||
FileLister
|
FileLister
|
||||||
|
|
|
@ -607,6 +607,10 @@ func TestRequestSymlink(t *testing.T) {
|
||||||
for _, s := range symlinks {
|
for _, s := range symlinks {
|
||||||
err := p.cli.Symlink(s.target, s.name)
|
err := p.cli.Symlink(s.target, s.name)
|
||||||
require.NoError(t, err, "Creating symlink %q with target %q failed", s.name, s.target)
|
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
|
// 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)
|
return filecmd(handlers.FileCmd, r, pkt)
|
||||||
case "List":
|
case "List":
|
||||||
return filelist(handlers.FileList, r, pkt)
|
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)
|
return filestat(handlers.FileList, r, pkt)
|
||||||
default:
|
default:
|
||||||
return statusFromError(pkt.id(), fmt.Errorf("unexpected method: %s", r.Method))
|
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
|
// init attributes of request object from packet data
|
||||||
func requestMethod(p requestPacket) (method string) {
|
func requestMethod(p requestPacket) (method string) {
|
||||||
switch p.(type) {
|
switch p.(type) {
|
||||||
|
|
Loading…
Reference in New Issue