Refactor tests and make `toLocalPath` a method on `Server`

This commit is contained in:
Mathias Fredriksson 2022-10-18 18:15:21 +03:00
parent b3f237d7ad
commit c93b0a0af2
11 changed files with 273 additions and 192 deletions

View File

@ -1247,7 +1247,7 @@ func (p *sshFxpExtendedPacketPosixRename) UnmarshalBinary(b []byte) error {
} }
func (p *sshFxpExtendedPacketPosixRename) respond(s *Server) responsePacket { func (p *sshFxpExtendedPacketPosixRename) respond(s *Server) responsePacket {
err := os.Rename(toLocalPath(s.workDir, p.Oldpath), toLocalPath(s.workDir, p.Newpath)) err := os.Rename(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath))
return statusFromError(p.ID, err) return statusFromError(p.ID, err)
} }
@ -1276,6 +1276,6 @@ func (p *sshFxpExtendedPacketHardlink) UnmarshalBinary(b []byte) error {
} }
func (p *sshFxpExtendedPacketHardlink) respond(s *Server) responsePacket { func (p *sshFxpExtendedPacketHardlink) respond(s *Server) responsePacket {
err := os.Link(toLocalPath(s.workDir, p.Oldpath), toLocalPath(s.workDir, p.Newpath)) err := os.Link(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath))
return statusFromError(p.ID, err) return statusFromError(p.ID, err)
} }

View File

@ -3,8 +3,6 @@
package sftp package sftp
import ( import (
"path"
"path/filepath"
"syscall" "syscall"
) )
@ -15,24 +13,3 @@ func fakeFileInfoSys() interface{} {
func testOsSys(sys interface{}) error { func testOsSys(sys interface{}) error {
return nil return nil
} }
func toLocalPath(workDir, p string) string {
if workDir != "" && !path.IsAbs(p) {
p = path.Join(workDir, p)
}
lp := filepath.FromSlash(p)
if path.IsAbs(p) {
tmp := lp[1:]
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/#s/boot" to "#s/boot"
return tmp
}
}
return lp
}

View File

@ -4,7 +4,6 @@ package sftp
import ( import (
"errors" "errors"
"path"
"syscall" "syscall"
) )
@ -22,11 +21,3 @@ func testOsSys(sys interface{}) error {
} }
return nil return nil
} }
func toLocalPath(workDir, p string) string {
if workDir != "" && !path.IsAbs(p) {
p = path.Join(workDir, p)
}
return p
}

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"io" "io"
"os" "os"
"runtime"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -248,113 +247,3 @@ func TestOpendirHandleReuse(t *testing.T) {
rpkt = request.call(handlers, pkt, nil, 0) rpkt = request.call(handlers, pkt, nil, 0)
assert.IsType(t, &sshFxpNamePacket{}, rpkt) assert.IsType(t, &sshFxpNamePacket{}, rpkt)
} }
func Test_toLocalPath(t *testing.T) {
type args struct {
workDir string
p string
}
tests := []struct {
name string
goos string
args args
want string
}{
{
name: "empty path with no workdir",
args: args{p: ""},
want: "",
},
{
name: "relative path with no workdir",
args: args{p: "file"},
want: "file",
},
{
name: "absolute path with no workdir",
args: args{p: "/file"},
want: "/file",
},
{
name: "workdir and empty path on Unix",
goos: "linux",
args: args{workDir: cleanPath("/home/user"), p: ""},
want: "/home/user",
},
{
name: "workdir and relative path on Unix",
goos: "linux",
args: args{workDir: cleanPath("/home/user"), p: "file"},
want: "/home/user/file",
},
{
name: "workdir and relative path with . on Unix",
goos: "linux",
args: args{workDir: cleanPath("/home/user"), p: "."},
want: "/home/user",
},
{
name: "workdir and relative path with . and file on Unix",
goos: "linux",
args: args{workDir: cleanPath("/home/user"), p: "./file"},
want: "/home/user/file",
},
{
name: "workdir and absolute path on Unix",
goos: "linux",
args: args{workDir: cleanPath("/home/user"), p: "/file"},
want: "/file",
},
{
name: "workdir and non-unixy path on Unix prefixes workdir",
goos: "linux",
args: args{workDir: cleanPath("/home/user"), p: "C:\\file"},
want: "/home/user/C:\\file",
},
{
name: "workdir and empty path on Windows",
goos: "windows",
args: args{workDir: cleanPath("C:\\Users\\User"), p: ""},
want: "C:\\Users\\User",
},
{
name: "workdir and relative path on Windows",
goos: "windows",
args: args{workDir: cleanPath("C:\\Users\\User"), p: "file"},
want: "C:\\Users\\User\\file",
},
{
name: "workdir and relative path with . on Windows",
goos: "windows",
args: args{workDir: cleanPath("C:\\Users\\User"), p: "."},
want: "C:\\Users\\User",
},
{
name: "workdir and relative path with . and file on Windows",
goos: "windows",
args: args{workDir: cleanPath("C:\\Users\\User"), p: "./file"},
want: "C:\\Users\\User\\file",
},
{
name: "workdir and absolute path on Windows",
goos: "windows",
args: args{workDir: cleanPath("C:\\Users\\User"), p: "/C:/file"},
want: "C:\\file",
},
{
name: "workdir and non-unixy path on Windows prefixes workdir",
goos: "windows",
args: args{workDir: cleanPath("C:\\Users\\User"), p: "C:\\file"},
want: "C:\\Users\\User\\C:\\file",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.goos != "" && runtime.GOOS != tt.goos {
t.Skipf("Skipping test for %s on %s", tt.goos, runtime.GOOS)
}
assert.Equal(t, tt.want, toLocalPath(tt.args.workDir, tt.args.p), "wrong local path")
})
}
}

View File

@ -1,8 +1,6 @@
package sftp package sftp
import ( import (
"path"
"path/filepath"
"syscall" "syscall"
) )
@ -13,36 +11,3 @@ func fakeFileInfoSys() interface{} {
func testOsSys(sys interface{}) error { func testOsSys(sys interface{}) error {
return nil return nil
} }
func toLocalPath(workDir, p string) string {
if workDir != "" && !path.IsAbs(p) {
p = path.Join(workDir, p)
}
lp := filepath.FromSlash(p)
if path.IsAbs(p) {
tmp := lp
for len(tmp) > 0 && tmp[0] == '\\' {
tmp = tmp[1:]
}
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/C:/Windows" to "C:\\Windows"
return tmp
}
tmp += "\\"
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes but with extra end slash is absolute,
// then we have a filepath encoded with a prefix '/' and a dropped '/' at the end.
// e.g. "/C:" to "C:\\"
return tmp
}
}
return lp
}

View File

@ -185,7 +185,7 @@ func handlePacket(s *Server, p orderedRequest) error {
} }
case *sshFxpStatPacket: case *sshFxpStatPacket:
// stat the requested file // stat the requested file
info, err := os.Stat(toLocalPath(s.workDir, p.Path)) info, err := os.Stat(s.toLocalPath(p.Path))
rpkt = &sshFxpStatResponse{ rpkt = &sshFxpStatResponse{
ID: p.ID, ID: p.ID,
info: info, info: info,
@ -195,7 +195,7 @@ func handlePacket(s *Server, p orderedRequest) error {
} }
case *sshFxpLstatPacket: case *sshFxpLstatPacket:
// stat the requested file // stat the requested file
info, err := os.Lstat(toLocalPath(s.workDir, p.Path)) info, err := os.Lstat(s.toLocalPath(p.Path))
rpkt = &sshFxpStatResponse{ rpkt = &sshFxpStatResponse{
ID: p.ID, ID: p.ID,
info: info, info: info,
@ -219,24 +219,24 @@ func handlePacket(s *Server, p orderedRequest) error {
} }
case *sshFxpMkdirPacket: case *sshFxpMkdirPacket:
// TODO FIXME: ignore flags field // TODO FIXME: ignore flags field
err := os.Mkdir(toLocalPath(s.workDir, p.Path), 0o755) err := os.Mkdir(s.toLocalPath(p.Path), 0o755)
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
case *sshFxpRmdirPacket: case *sshFxpRmdirPacket:
err := os.Remove(toLocalPath(s.workDir, p.Path)) err := os.Remove(s.toLocalPath(p.Path))
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
case *sshFxpRemovePacket: case *sshFxpRemovePacket:
err := os.Remove(toLocalPath(s.workDir, p.Filename)) err := os.Remove(s.toLocalPath(p.Filename))
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
case *sshFxpRenamePacket: case *sshFxpRenamePacket:
err := os.Rename(toLocalPath(s.workDir, p.Oldpath), toLocalPath(s.workDir, p.Newpath)) err := os.Rename(s.toLocalPath(p.Oldpath), s.toLocalPath(p.Newpath))
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
case *sshFxpSymlinkPacket: case *sshFxpSymlinkPacket:
err := os.Symlink(toLocalPath(s.workDir, p.Targetpath), toLocalPath(s.workDir, p.Linkpath)) err := os.Symlink(s.toLocalPath(p.Targetpath), s.toLocalPath(p.Linkpath))
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
case *sshFxpClosePacket: case *sshFxpClosePacket:
rpkt = statusFromError(p.ID, s.closeHandle(p.Handle)) rpkt = statusFromError(p.ID, s.closeHandle(p.Handle))
case *sshFxpReadlinkPacket: case *sshFxpReadlinkPacket:
f, err := os.Readlink(toLocalPath(s.workDir, p.Path)) f, err := os.Readlink(s.toLocalPath(p.Path))
rpkt = &sshFxpNamePacket{ rpkt = &sshFxpNamePacket{
ID: p.ID, ID: p.ID,
NameAttrs: []*sshFxpNameAttr{ NameAttrs: []*sshFxpNameAttr{
@ -251,7 +251,7 @@ func handlePacket(s *Server, p orderedRequest) error {
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
} }
case *sshFxpRealpathPacket: case *sshFxpRealpathPacket:
f, err := filepath.Abs(toLocalPath(s.workDir, p.Path)) f, err := filepath.Abs(s.toLocalPath(p.Path))
f = cleanPath(f) f = cleanPath(f)
rpkt = &sshFxpNamePacket{ rpkt = &sshFxpNamePacket{
ID: p.ID, ID: p.ID,
@ -267,7 +267,7 @@ func handlePacket(s *Server, p orderedRequest) error {
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
} }
case *sshFxpOpendirPacket: case *sshFxpOpendirPacket:
lp := toLocalPath(s.workDir, p.Path) lp := s.toLocalPath(p.Path)
if stat, err := os.Stat(lp); err != nil { if stat, err := os.Stat(lp); err != nil {
rpkt = statusFromError(p.ID, err) rpkt = statusFromError(p.ID, err)
@ -458,7 +458,7 @@ func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket {
osFlags |= os.O_EXCL osFlags |= os.O_EXCL
} }
f, err := os.OpenFile(toLocalPath(svr.workDir, p.Path), osFlags, 0o644) f, err := os.OpenFile(svr.toLocalPath(p.Path), osFlags, 0o644)
if err != nil { if err != nil {
return statusFromError(p.ID, err) return statusFromError(p.ID, err)
} }
@ -496,7 +496,7 @@ func (p *sshFxpSetstatPacket) respond(svr *Server) responsePacket {
b := p.Attrs.([]byte) b := p.Attrs.([]byte)
var err error var err error
p.Path = toLocalPath(svr.workDir, p.Path) p.Path = svr.toLocalPath(p.Path)
debug("setstat name \"%s\"", p.Path) debug("setstat name \"%s\"", p.Path)
if (p.Flags & sshFileXferAttrSize) != 0 { if (p.Flags & sshFileXferAttrSize) != 0 {

30
server_plan9.go Normal file
View File

@ -0,0 +1,30 @@
//go:build plan9
// +build plan9
package sftp
import (
"path"
"path/filepath"
)
func (s *Server) toLocalPath(p string) string {
if s.workDir != "" && !path.IsAbs(p) {
p = path.Join(s.workDir, p)
}
lp := filepath.FromSlash(p)
if path.IsAbs(p) {
tmp := lp[1:]
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/#s/boot" to "#s/boot"
return tmp
}
}
return lp
}

16
server_unix.go Normal file
View File

@ -0,0 +1,16 @@
//go:build !windows && !plan9
// +build !windows,!plan9
package sftp
import (
"path"
)
func (s *Server) toLocalPath(p string) string {
if s.workDir != "" && !path.IsAbs(p) {
p = path.Join(s.workDir, p)
}
return p
}

87
server_unix_test.go Normal file
View File

@ -0,0 +1,87 @@
//go:build !windows && !plan9
// +build !windows,!plan9
package sftp
import (
"testing"
)
func TestServer_toLocalPath(t *testing.T) {
tests := []struct {
name string
withWorkDir string
p string
want string
}{
{
name: "empty path with no workdir",
p: "",
want: "",
},
{
name: "relative path with no workdir",
p: "file",
want: "file",
},
{
name: "absolute path with no workdir",
p: "/file",
want: "/file",
},
{
name: "workdir and empty path",
withWorkDir: "/home/user",
p: "",
want: "/home/user",
},
{
name: "workdir and relative path",
withWorkDir: "/home/user",
p: "file",
want: "/home/user/file",
},
{
name: "workdir and relative path with .",
withWorkDir: "/home/user",
p: ".",
want: "/home/user",
},
{
name: "workdir and relative path with . and file",
withWorkDir: "/home/user",
p: "./file",
want: "/home/user/file",
},
{
name: "workdir and absolute path",
withWorkDir: "/home/user",
p: "/file",
want: "/file",
},
{
name: "workdir and non-unixy path prefixes workdir",
withWorkDir: "/home/user",
p: "C:\\file",
// This may look like a bug but it is the result of passing
// invalid input (a non-unixy path) to the server.
want: "/home/user/C:\\file",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// We don't need to initialize the Server further to test
// toLocalPath behavior.
s := &Server{}
if tt.withWorkDir != "" {
if err := WithServerWorkingDirectory(tt.withWorkDir)(s); err != nil {
t.Fatal(err)
}
}
if got := s.toLocalPath(tt.p); got != tt.want {
t.Errorf("Server.toLocalPath() = %v, want %v", got, tt.want)
}
})
}
}

39
server_windows.go Normal file
View File

@ -0,0 +1,39 @@
package sftp
import (
"path"
"path/filepath"
)
func (s *Server) toLocalPath(p string) string {
if s.workDir != "" && !path.IsAbs(p) {
p = path.Join(s.workDir, p)
}
lp := filepath.FromSlash(p)
if path.IsAbs(p) {
tmp := lp
for len(tmp) > 0 && tmp[0] == '\\' {
tmp = tmp[1:]
}
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/C:/Windows" to "C:\\Windows"
return tmp
}
tmp += "\\"
if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes but with extra end slash is absolute,
// then we have a filepath encoded with a prefix '/' and a dropped '/' at the end.
// e.g. "/C:" to "C:\\"
return tmp
}
}
return lp
}

87
server_windows_test.go Normal file
View File

@ -0,0 +1,87 @@
//go:build windows
// +build windows
package sftp
import (
"testing"
)
func TestServer_toLocalPath(t *testing.T) {
tests := []struct {
name string
withWorkDir string
p string
want string
}{
{
name: "empty path with no workdir",
p: "",
want: "",
},
{
name: "relative path with no workdir",
p: "file",
want: "file",
},
{
name: "absolute path with no workdir",
p: "/file",
want: "\\file",
},
{
name: "workdir and empty path",
withWorkDir: "C:\\Users\\User",
p: "",
want: "C:\\Users\\User",
},
{
name: "workdir and relative path",
withWorkDir: "C:\\Users\\User",
p: "file",
want: "C:\\Users\\User\\file",
},
{
name: "workdir and relative path with .",
withWorkDir: "C:\\Users\\User",
p: ".",
want: "C:\\Users\\User",
},
{
name: "workdir and relative path with . and file",
withWorkDir: "C:\\Users\\User",
p: "./file",
want: "C:\\Users\\User\\file",
},
{
name: "workdir and absolute path",
withWorkDir: "C:\\Users\\User",
p: "/C:/file",
want: "C:\\file",
},
{
name: "workdir and non-unixy path prefixes workdir",
withWorkDir: "C:\\Users\\User",
p: "C:\\file",
// This may look like a bug but it is the result of passing
// invalid input (a non-unixy path) to the server.
want: "C:\\Users\\User\\C:\\file",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// We don't need to initialize the Server further to test
// toLocalPath behavior.
s := &Server{}
if tt.withWorkDir != "" {
if err := WithServerWorkingDirectory(tt.withWorkDir)(s); err != nil {
t.Fatal(err)
}
}
if got := s.toLocalPath(tt.p); got != tt.want {
t.Errorf("Server.toLocalPath() = %v, want %v", got, tt.want)
}
})
}
}