Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RealPathFileLister: allow to return an error #516

Merged
merged 1 commit into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 2 additions & 11 deletions request-example.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,19 +464,10 @@ 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
mockErr error
startDirectory string
rootFile *memFile
mockErr error

mu sync.Mutex
files map[string]*memFile
Expand Down
19 changes: 16 additions & 3 deletions request-interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,25 @@ 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.
// Up to v1.13.5 the signature for the RealPath method was:
//
// RealPath(string) string
//
// we have added a legacyRealPathFileLister that implements the old method
// to ensure that your code does not break.
// You should use the new method signature to avoid future issues
type RealPathFileLister interface {
FileLister
RealPath(string) (string, error)
}

// This interface is here for backward compatibility only
type legacyRealPathFileLister interface {
FileLister
RealPath(string) string
}
Expand Down
17 changes: 13 additions & 4 deletions request-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,21 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR
rpkt = statusFromError(pkt.ID, rs.closeRequest(handle))
case *sshFxpRealpathPacket:
var realPath string
if realPather, ok := rs.Handlers.FileList.(RealPathFileLister); ok {
realPath = realPather.RealPath(pkt.getPath())
} else {
var err error

switch pather := rs.Handlers.FileList.(type) {
case RealPathFileLister:
realPath, err = pather.RealPath(pkt.getPath())
case legacyRealPathFileLister:
realPath = pather.RealPath(pkt.getPath())
default:
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)
Expand Down
109 changes: 93 additions & 16 deletions request-server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (cs csPair) testHandler() *root {

const sock = "/tmp/rstest.sock"

func clientRequestServerPair(t *testing.T, options ...RequestServerOption) *csPair {
func clientRequestServerPairWithHandlers(t *testing.T, handlers Handlers, options ...RequestServerOption) *csPair {
skipIfWindows(t)
skipIfPlan9(t)

Expand All @@ -61,7 +61,6 @@ func clientRequestServerPair(t *testing.T, options ...RequestServerOption) *csPa
fd, err := l.Accept()
require.NoError(t, err)

handlers := InMemHandler()
if *testAllocator {
options = append(options, WithRSAllocator())
}
Expand Down Expand Up @@ -90,6 +89,10 @@ func clientRequestServerPair(t *testing.T, options ...RequestServerOption) *csPa
return pair
}

func clientRequestServerPair(t *testing.T, options ...RequestServerOption) *csPair {
return clientRequestServerPairWithHandlers(t, InMemHandler(), options...)
}

func checkRequestServerAllocator(t *testing.T, p *csPair) {
if p.svr.pktMgr.alloc == nil {
return
Expand Down Expand Up @@ -840,22 +843,96 @@ 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",
startDir := "/startdir"
// the default InMemHandler does not implement the RealPathFileLister interface
// so we are using the builtin implementation here
p := clientRequestServerPair(t, WithStartDirectory(startDir))
defer p.Close()

realPath, err := p.cli.RealPath(".")
require.NoError(t, err)
assert.Equal(t, startDir, realPath)
realPath, err = p.cli.RealPath("/")
require.NoError(t, err)
assert.Equal(t, "/", realPath)
realPath, err = p.cli.RealPath("..")
require.NoError(t, err)
assert.Equal(t, "/", realPath)
realPath, err = p.cli.RealPath("../../..")
require.NoError(t, err)
assert.Equal(t, "/", realPath)
// test a relative path
realPath, err = p.cli.RealPath("relpath")
require.NoError(t, err)
assert.Equal(t, path.Join(startDir, "relpath"), realPath)
}

// In memory file-system which implements RealPathFileLister
type rootWithRealPather struct {
root
}

// implements RealpathFileLister interface
func (fs *rootWithRealPather) RealPath(p string) (string, error) {
if fs.mockErr != nil {
return "", fs.mockErr
}
return cleanPath(p), nil
}

func TestRealPathFileLister(t *testing.T) {
root := &rootWithRealPather{
root: root{
rootFile: &memFile{name: "/", modtime: time.Now(), isdir: true},
files: make(map[string]*memFile),
},
}
handlers := Handlers{root, root, root, root}
p := clientRequestServerPairWithHandlers(t, handlers)
defer p.Close()

realPath, err := p.cli.RealPath(".")
require.NoError(t, err)
assert.Equal(t, "/", realPath)
realPath, err = p.cli.RealPath("relpath")
require.NoError(t, err)
assert.Equal(t, "/relpath", realPath)
// test an error
root.returnErr(ErrSSHFxPermissionDenied)
_, err = p.cli.RealPath("/")
require.ErrorIs(t, err, os.ErrPermission)
}

// In memory file-system which implements legacyRealPathFileLister
type rootWithLegacyRealPather struct {
root
}

p := root.Realpath(".")
assert.Equal(t, root.startDirectory, p)
p = root.Realpath("/")
assert.Equal(t, "/", p)
p = root.Realpath("..")
assert.Equal(t, "/", p)
p = root.Realpath("../../..")
assert.Equal(t, "/", p)
p = root.Realpath("relpath")
assert.Equal(t, path.Join(root.startDirectory, "relpath"), p)
// implements RealpathFileLister interface
func (fs *rootWithLegacyRealPather) RealPath(p string) string {
return cleanPath(p)
}

func TestLegacyRealPathFileLister(t *testing.T) {
root := &rootWithLegacyRealPather{
root: root{
rootFile: &memFile{name: "/", modtime: time.Now(), isdir: true},
files: make(map[string]*memFile),
},
}
handlers := Handlers{root, root, root, root}
p := clientRequestServerPairWithHandlers(t, handlers)
defer p.Close()

realPath, err := p.cli.RealPath(".")
require.NoError(t, err)
assert.Equal(t, "/", realPath)
realPath, err = p.cli.RealPath("..")
require.NoError(t, err)
assert.Equal(t, "/", realPath)
realPath, err = p.cli.RealPath("relpath")
require.NoError(t, err)
assert.Equal(t, "/relpath", realPath)
}

func TestCleanPath(t *testing.T) {
Expand Down