Skip to content

Commit

Permalink
Merge pull request #498 from drakkan/startdir
Browse files Browse the repository at this point in the history
request server: add WithStartDirectory option
  • Loading branch information
drakkan committed Mar 3, 2022
2 parents aa9a37d + 03407a7 commit 59b2472
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 17 deletions.
2 changes: 2 additions & 0 deletions request-interfaces.go
Expand Up @@ -90,6 +90,8 @@ type LstatFileLister interface {
// 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.
//
// Deprecated: if you want to set a start directory use WithStartDirectory RequestServerOption instead.
type RealPathFileLister interface {
FileLister
RealPath(string) string
Expand Down
42 changes: 33 additions & 9 deletions request-server.go
Expand Up @@ -27,6 +27,8 @@ type RequestServer struct {
*serverConn
pktMgr *packetManager

startDirectory string

mu sync.RWMutex
handleCount int
openRequests map[string]*Request
Expand All @@ -47,6 +49,14 @@ func WithRSAllocator() RequestServerOption {
}
}

// WithStartDirectory sets a start directory to use as base for relative paths.
// If unset the default is "/"
func WithStartDirectory(startDirectory string) RequestServerOption {
return func(rs *RequestServer) {
rs.startDirectory = cleanPath(startDirectory)
}
}

// NewRequestServer creates/allocates/returns new RequestServer.
// Normally there will be one server per user-session.
func NewRequestServer(rwc io.ReadWriteCloser, h Handlers, options ...RequestServerOption) *RequestServer {
Expand All @@ -62,6 +72,8 @@ func NewRequestServer(rwc io.ReadWriteCloser, h Handlers, options ...RequestServ
serverConn: svrConn,
pktMgr: newPktMgr(svrConn),

startDirectory: "/",

openRequests: make(map[string]*Request),
}

Expand Down Expand Up @@ -210,19 +222,19 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR
if realPather, ok := rs.Handlers.FileList.(RealPathFileLister); ok {
realPath = realPather.RealPath(pkt.getPath())
} else {
realPath = cleanPath(pkt.getPath())
realPath = cleanPathWithBase(rs.startDirectory, pkt.getPath())
}
rpkt = cleanPacketPath(pkt, realPath)
case *sshFxpOpendirPacket:
request := requestFromPacket(ctx, pkt)
request := requestFromPacket(ctx, pkt, rs.startDirectory)
handle := rs.nextRequest(request)
rpkt = request.opendir(rs.Handlers, pkt)
if _, ok := rpkt.(*sshFxpHandlePacket); !ok {
// if we return an error we have to remove the handle from the active ones
rs.closeRequest(handle)
}
case *sshFxpOpenPacket:
request := requestFromPacket(ctx, pkt)
request := requestFromPacket(ctx, pkt, rs.startDirectory)
handle := rs.nextRequest(request)
rpkt = request.open(rs.Handlers, pkt)
if _, ok := rpkt.(*sshFxpHandlePacket); !ok {
Expand All @@ -235,7 +247,10 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR
if !ok {
rpkt = statusFromError(pkt.ID, EBADF)
} else {
request = NewRequest("Stat", request.Filepath)
request = &Request{
Method: "Stat",
Filepath: cleanPathWithBase(rs.startDirectory, request.Filepath),
}
rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
}
case *sshFxpFsetstatPacket:
Expand All @@ -244,15 +259,24 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR
if !ok {
rpkt = statusFromError(pkt.ID, EBADF)
} else {
request = NewRequest("Setstat", request.Filepath)
request = &Request{
Method: "Setstat",
Filepath: cleanPathWithBase(rs.startDirectory, request.Filepath),
}
rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
}
case *sshFxpExtendedPacketPosixRename:
request := NewRequest("PosixRename", pkt.Oldpath)
request.Target = pkt.Newpath
request := &Request{
Method: "PosixRename",
Filepath: cleanPathWithBase(rs.startDirectory, pkt.Oldpath),
Target: cleanPathWithBase(rs.startDirectory, pkt.Newpath),
}
rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
case *sshFxpExtendedPacketStatVFS:
request := NewRequest("StatVFS", pkt.Path)
request := &Request{
Method: "StatVFS",
Filepath: cleanPathWithBase(rs.startDirectory, pkt.Path),
}
rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
case hasHandle:
handle := pkt.getHandle()
Expand All @@ -263,7 +287,7 @@ func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedR
rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
}
case hasPath:
request := requestFromPacket(ctx, pkt)
request := requestFromPacket(ctx, pkt, rs.startDirectory)
rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
request.close()
default:
Expand Down
35 changes: 33 additions & 2 deletions request-server_test.go
Expand Up @@ -37,7 +37,7 @@ func (cs csPair) testHandler() *root {

const sock = "/tmp/rstest.sock"

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

Expand All @@ -62,7 +62,6 @@ func clientRequestServerPair(t *testing.T) *csPair {
require.NoError(t, err)

handlers := InMemHandler()
var options []RequestServerOption
if *testAllocator {
options = append(options, WithRSAllocator())
}
Expand Down Expand Up @@ -781,6 +780,37 @@ func TestRequestStatVFSError(t *testing.T) {
checkRequestServerAllocator(t, p)
}

func TestRequestStartDirOption(t *testing.T) {
startDir := "/start/dir"
p := clientRequestServerPair(t, WithStartDirectory(startDir))
defer p.Close()

// create the start directory
err := p.cli.MkdirAll(startDir)
require.NoError(t, err)
// the working directory must be the defined start directory
wd, err := p.cli.Getwd()
require.NoError(t, err)
require.Equal(t, startDir, wd)
// upload a file using a relative path, it must be uploaded to the start directory
fileName := "file.txt"
_, err = putTestFile(p.cli, fileName, "")
require.NoError(t, err)
// we must be able to stat the file using both a relative and an absolute path
for _, filePath := range []string{fileName, path.Join(startDir, fileName)} {
fi, err := p.cli.Stat(filePath)
require.NoError(t, err)
assert.Equal(t, fileName, fi.Name())
}
// list dir contents using a relative path
entries, err := p.cli.ReadDir(".")
assert.NoError(t, err)
assert.Len(t, entries, 1)
// delete the file using a relative path
err = p.cli.Remove(fileName)
assert.NoError(t, err)
}

func TestCleanDisconnect(t *testing.T) {
p := clientRequestServerPair(t)
defer p.Close()
Expand Down Expand Up @@ -831,6 +861,7 @@ func TestRealPath(t *testing.T) {
func TestCleanPath(t *testing.T) {
assert.Equal(t, "/", cleanPath("/"))
assert.Equal(t, "/", cleanPath("."))
assert.Equal(t, "/", cleanPath(""))
assert.Equal(t, "/", cleanPath("/."))
assert.Equal(t, "/", cleanPath("/a/.."))
assert.Equal(t, "/a/c", cleanPath("/a/b/../c"))
Expand Down
14 changes: 8 additions & 6 deletions request.go
Expand Up @@ -168,9 +168,11 @@ func (r *Request) copy() *Request {
}

// New Request initialized based on packet data
func requestFromPacket(ctx context.Context, pkt hasPath) *Request {
method := requestMethod(pkt)
request := NewRequest(method, pkt.getPath())
func requestFromPacket(ctx context.Context, pkt hasPath, baseDir string) *Request {
request := &Request{
Method: requestMethod(pkt),
Filepath: cleanPathWithBase(baseDir, pkt.getPath()),
}
request.ctx, request.cancelCtx = context.WithCancel(ctx)

switch p := pkt.(type) {
Expand All @@ -180,13 +182,13 @@ func requestFromPacket(ctx context.Context, pkt hasPath) *Request {
request.Flags = p.Flags
request.Attrs = p.Attrs.([]byte)
case *sshFxpRenamePacket:
request.Target = cleanPath(p.Newpath)
request.Target = cleanPathWithBase(baseDir, p.Newpath)
case *sshFxpSymlinkPacket:
// NOTE: given a POSIX compliant signature: symlink(target, linkpath string)
// this makes Request.Target the linkpath, and Request.Filepath the target.
request.Target = cleanPath(p.Linkpath)
request.Target = cleanPathWithBase(baseDir, p.Linkpath)
case *sshFxpExtendedPacketHardlink:
request.Target = cleanPath(p.Newpath)
request.Target = cleanPathWithBase(baseDir, p.Newpath)
}
return request
}
Expand Down

0 comments on commit 59b2472

Please sign in to comment.