add Realpath client public fn and fix server impl
diff --git a/client.go b/client.go
index f0d67e8..4aee6dd 100644
--- a/client.go
+++ b/client.go
@@ -611,6 +611,11 @@
}
}
+// Realpath requests the canonical name (absolute path, symlinks resolved) of a requested path
+func (c *Client) Realpath(path string) (string, error) {
+ return c.realpath(path)
+}
+
func (c *Client) realpath(path string) (string, error) {
id := c.nextID()
typ, data, err := c.sendRequest(sshFxpRealpathPacket{
diff --git a/client_integration_test.go b/client_integration_test.go
index 8fa7f95..6f3f537 100644
--- a/client_integration_test.go
+++ b/client_integration_test.go
@@ -578,6 +578,47 @@
}
}
+func TestClientRealpath(t *testing.T) {
+ sftp, cmd := testClient(t, READONLY, NO_DELAY)
+ defer cmd.Wait()
+ defer sftp.Close()
+
+ f, err := ioutil.TempFile("", "sftptest")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(f.Name())
+
+ realName, err := filepath.EvalSymlinks(f.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+ realName, err = filepath.Abs(realName)
+ if err != nil {
+ t.Fatal(err)
+ }
+ realName = filepath.Clean(realName)
+ linkName := realName + ".softlink"
+
+ // create a symlink that points at sftptest
+ if err := os.Symlink(realName, linkName); err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(linkName)
+
+ // compare names
+ want := realName
+ got, err := sftp.Realpath(linkName)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // check that realpath is valid
+ if want != got {
+ t.Fatalf("Realpath(%q): got %v", want, got)
+ }
+}
+
func TestClientSymlink(t *testing.T) {
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
defer cmd.Wait()
diff --git a/server.go b/server.go
index 81b1659..1c6f0ae 100644
--- a/server.go
+++ b/server.go
@@ -361,7 +361,12 @@
func (p sshFxpRealpathPacket) readonly() bool { return true }
func (p sshFxpRealpathPacket) respond(svr *Server) error {
- f, err := filepath.Abs(p.Path)
+ f, err := filepath.EvalSymlinks(p.Path)
+ if err != nil {
+ return svr.sendPacket(statusFromError(p.ID, err))
+ }
+
+ f, err = filepath.Abs(f)
if err != nil {
return svr.sendPacket(statusFromError(p.ID, err))
}
diff --git a/server_integration_test.go b/server_integration_test.go
index 4d70829..7772ac1 100644
--- a/server_integration_test.go
+++ b/server_integration_test.go
@@ -503,6 +503,63 @@
}
}
+func TestServerRealpath(t *testing.T) {
+ // create servers of each variant
+ listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
+ listenerOp, hostOp, portOp := testServer(t, OPENSSH_SFTP, READONLY)
+ defer listenerGo.Close()
+ defer listenerOp.Close()
+
+ // create clients for each server
+ clientConfig := ssh.ClientConfig{
+ Config: ssh.Config{ },
+ User: "test",
+ Auth: []ssh.AuthMethod{ssh.Password("")},
+ }
+ clientConfigGo := clientConfig
+ clientConfigOp := clientConfig
+ sshClientGo, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", hostGo, portGo), &clientConfigGo)
+ if err != nil {
+ t.Fatal(err)
+ }
+ sshClientOp, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", hostOp, portOp), &clientConfigOp)
+ if err != nil {
+ t.Fatal(err)
+ }
+ sftpClientGo, err := NewClient(sshClientGo)
+ if err != nil {
+ t.Fatal(err)
+ }
+ sftpClientOp, err := NewClient(sshClientOp)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // create a file, and a convoluted path under it
+ dir, err := ioutil.TempDir("", "sftptestServerRealpath")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+ f1 := path.Join(dir, "a", "b", "b.txt")
+ f1strange := strings.Join([]string{dir, "a", "b", "c", "d", "e", "..", "..", "..", "b.txt"}, string(os.PathSeparator))
+ if err := os.MkdirAll(path.Join(dir, "a", "b", "c", "d", "e"), 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err := ioutil.WriteFile(f1, []byte("hello"), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ // compare output from go and openssh
+ if outputGo, err := sftpClientGo.Realpath(f1strange); err != nil {
+ t.Fatalf("failed: %v %v", err, string(outputGo))
+ } else if outputOp, err := sftpClientOp.Realpath(f1strange); err != nil {
+ t.Fatalf("failed: %v %v", err, string(outputOp))
+ } else if outputGo != outputOp {
+ t.Fatalf("output differs:\ngo: '%v'\nop: '%v'\n", outputGo, outputOp)
+ }
+}
+
func TestServerPut(t *testing.T) {
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
defer listenerGo.Close()