Skip to content

Commit

Permalink
request server: add an optional RealpathFileLister interface
Browse files Browse the repository at this point in the history
This allow to customize the responses for SSH_FXP_REALPATH requests
and so implementing features like a start directory
  • Loading branch information
drakkan committed Apr 27, 2021
1 parent 2b80967 commit db2d413
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 8 deletions.
13 changes: 11 additions & 2 deletions request-example.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,10 +464,19 @@ 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(p, fs.startDirectory)
}

// In memory file-system-y thing that the Hanlders live on
type root struct {
rootFile *memFile
mockErr error
rootFile *memFile
mockErr error
startDirectory string

mu sync.Mutex
files map[string]*memFile
Expand Down
9 changes: 9 additions & 0 deletions request-interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ type LstatFileLister interface {
Lstat(*Request) (ListerAt, error)
}

// 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.
// You have to return an absolute POSIX path.
type RealpathFileLister interface {
FileLister
Realpath(string) string
}

// ListerAt does for file lists what io.ReaderAt does for files.
// ListAt should return the number of entries copied and an io.EOF
// error if at end of list. This is testable by comparing how many you
Expand Down
21 changes: 15 additions & 6 deletions request-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,13 @@ func (rs *RequestServer) packetWorker(
handle := pkt.getHandle()
rpkt = statusFromError(pkt.ID, rs.closeRequest(handle))
case *sshFxpRealpathPacket:
rpkt = cleanPacketPath(pkt)
var realPath string
if realPather, ok := rs.Handlers.FileList.(RealpathFileLister); ok {
realPath = realPather.Realpath(pkt.getPath())
} else {
realPath = cleanPath(pkt.getPath())
}
rpkt = cleanPacketPath(pkt, realPath)
case *sshFxpOpendirPacket:
request := requestFromPacket(ctx, pkt)
handle := rs.nextRequest(request)
Expand Down Expand Up @@ -263,14 +269,13 @@ func (rs *RequestServer) packetWorker(
}

// clean and return name packet for file
func cleanPacketPath(pkt *sshFxpRealpathPacket) responsePacket {
path := cleanPath(pkt.getPath())
func cleanPacketPath(pkt *sshFxpRealpathPacket, realPath string) responsePacket {
return &sshFxpNamePacket{
ID: pkt.id(),
NameAttrs: []*sshFxpNameAttr{
{
Name: path,
LongName: path,
Name: realPath,
LongName: realPath,
Attrs: emptyFileStat,
},
},
Expand All @@ -279,9 +284,13 @@ func cleanPacketPath(pkt *sshFxpRealpathPacket) responsePacket {

// Makes sure we have a clean POSIX (/) absolute path to work with
func cleanPath(p string) string {
return cleanPathWithBase(p, "/")
}

func cleanPathWithBase(p, base string) string {
p = filepath.ToSlash(p)
if !path.IsAbs(p) {
p = "/" + p
return path.Join(base, p)
}
return path.Clean(p)
}
20 changes: 20 additions & 0 deletions request-server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io/ioutil"
"net"
"os"
"path"
"runtime"
"testing"
"time"
Expand Down Expand Up @@ -807,6 +808,25 @@ func TestUncleanDisconnect(t *testing.T) {
checkRequestServerAllocator(t, p)
}

func TestRealPath(t *testing.T) {
root := &root{
rootFile: &memFile{name: "/", modtime: time.Now(), isdir: true},
files: make(map[string]*memFile),
startDirectory: "/apath",
}

p := root.Realpath(".")
require.Equal(t, root.startDirectory, p)
p = root.Realpath("/")
require.Equal(t, "/", p)
p = root.Realpath("..")
require.Equal(t, "/", p)
p = root.Realpath("../../..")
require.Equal(t, "/", p)
p = root.Realpath("relpath")
require.Equal(t, path.Join(root.startDirectory, "relpath"), p)
}

func TestCleanPath(t *testing.T) {
assert.Equal(t, "/", cleanPath("/"))
assert.Equal(t, "/", cleanPath("."))
Expand Down

0 comments on commit db2d413

Please sign in to comment.