diff --git a/request-example.go b/request-example.go index ba22bcd0..def6666f 100644 --- a/request-example.go +++ b/request-example.go @@ -464,14 +464,6 @@ func (fs *root) Lstat(r *Request) (ListerAt, error) { return listerat{file}, nil } -// implements RealpathFileLister interface -func (fs *root) Realpath(p string) string { - if fs.startDirectory == "" || fs.startDirectory == "/" { - return cleanPath(p) - } - return cleanPathWithBase(fs.startDirectory, p) -} - // In memory file-system-y thing that the Hanlders live on type root struct { rootFile *memFile @@ -555,6 +547,35 @@ func (fs *root) fetch(path string) (*memFile, error) { return file, nil } +// In memory file-system which implements the RealPathFileLister +type rootWithRealPather struct { + root +} + +// implements RealpathFileLister interface +func (fs *rootWithRealPather) Realpath(p string) (string, error) { + if fs.mockErr != nil { + return "", fs.mockErr + } + if fs.startDirectory == "" || fs.startDirectory == "/" { + return cleanPath(p), nil + } + return cleanPathWithBase(fs.startDirectory, p), nil +} + +// In memory file-system which implements legacyRealPathFileLister +type rootWithLegacyRealPather struct { + root +} + +// implements RealpathFileLister interface +func (fs *rootWithLegacyRealPather) Realpath(p string) string { + if fs.startDirectory == "" || fs.startDirectory == "/" { + return cleanPath(p) + } + return cleanPathWithBase(fs.startDirectory, p) +} + // Implements os.FileInfo, io.ReaderAt and io.WriterAt interfaces. // These are the 3 interfaces necessary for the Handlers. // Implements the optional interface TransferError. diff --git a/request-interfaces.go b/request-interfaces.go index e5dc49bb..d882b11a 100644 --- a/request-interfaces.go +++ b/request-interfaces.go @@ -87,12 +87,19 @@ type LstatFileLister interface { } // RealPathFileLister is a FileLister that implements the Realpath method. -// We use "/" as start directory for relative paths, implementing this -// interface you can customize the start directory. +// The built-in RealPath implementation does not resolve symbolic links, +// by implementing this interface you can customize the returned path +// and, for example, resolve symbolinc links if needed for your use case. // You have to return an absolute POSIX path. -// -// Deprecated: if you want to set a start directory use WithStartDirectory RequestServerOption instead. type RealPathFileLister interface { + FileLister + RealPath(string) (string, error) +} + +// This interface is here for backward compatibility only. +// Up to v1.13.5 we didn't allow to return an error from the RealPathFileLister +// interface, which is wrong +type legacyRealPathFileLister interface { FileLister RealPath(string) string } diff --git a/request-server.go b/request-server.go index b7dadd6c..197273d6 100644 --- a/request-server.go +++ b/request-server.go @@ -219,12 +219,19 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR rpkt = statusFromError(pkt.ID, rs.closeRequest(handle)) case *sshFxpRealpathPacket: var realPath string + var err error if realPather, ok := rs.Handlers.FileList.(RealPathFileLister); ok { - realPath = realPather.RealPath(pkt.getPath()) + realPath, err = realPather.RealPath(pkt.getPath()) + } else if legacyRealPather, ok := rs.Handlers.FileList.(legacyRealPathFileLister); ok { + realPath = legacyRealPather.RealPath(pkt.getPath()) } else { realPath = cleanPathWithBase(rs.startDirectory, pkt.getPath()) } - rpkt = cleanPacketPath(pkt, realPath) + if err != nil { + rpkt = statusFromError(pkt.ID, err) + } else { + rpkt = cleanPacketPath(pkt, realPath) + } case *sshFxpOpendirPacket: request := requestFromPacket(ctx, pkt, rs.startDirectory) handle := rs.nextRequest(request) diff --git a/request-server_test.go b/request-server_test.go index c8e64d63..2821ed2c 100644 --- a/request-server_test.go +++ b/request-server_test.go @@ -2,6 +2,7 @@ package sftp import ( "context" + "errors" "fmt" "io" "io/ioutil" @@ -840,10 +841,42 @@ func TestUncleanDisconnect(t *testing.T) { } func TestRealPath(t *testing.T) { - root := &root{ - rootFile: &memFile{name: "/", modtime: time.Now(), isdir: true}, - files: make(map[string]*memFile), - startDirectory: "/apath", + root := &rootWithRealPather{ + root: root{ + rootFile: &memFile{name: "/", modtime: time.Now(), isdir: true}, + files: make(map[string]*memFile), + startDirectory: "/apath", + }, + } + + p, err := root.Realpath(".") + require.NoError(t, err) + assert.Equal(t, root.startDirectory, p) + p, err = root.Realpath("/") + require.NoError(t, err) + assert.Equal(t, "/", p) + p, err = root.Realpath("..") + require.NoError(t, err) + assert.Equal(t, "/", p) + p, err = root.Realpath("../../..") + require.NoError(t, err) + assert.Equal(t, "/", p) + p, err = root.Realpath("relpath") + require.NoError(t, err) + assert.Equal(t, path.Join(root.startDirectory, "relpath"), p) + // test an error + root.returnErr(errors.New("test error")) + _, err = root.Realpath(".") + require.Error(t, err) +} + +func TestLegacyRealPath(t *testing.T) { + root := &rootWithLegacyRealPather{ + root: root{ + rootFile: &memFile{name: "/", modtime: time.Now(), isdir: true}, + files: make(map[string]*memFile), + startDirectory: "/apath", + }, } p := root.Realpath(".")