2016-06-13 19:37:38 +08:00
|
|
|
package sftp
|
|
|
|
|
2016-06-15 18:04:25 +08:00
|
|
|
import (
|
|
|
|
"io"
|
2017-04-05 17:08:03 +08:00
|
|
|
"sync"
|
2016-06-15 18:04:25 +08:00
|
|
|
"testing"
|
|
|
|
)
|
2016-06-13 19:37:38 +08:00
|
|
|
|
|
|
|
func clientServerPair(t *testing.T) (*Client, *Server) {
|
2016-06-15 18:04:25 +08:00
|
|
|
cr, sw := io.Pipe()
|
|
|
|
sr, cw := io.Pipe()
|
|
|
|
server, err := NewServer(struct {
|
|
|
|
io.Reader
|
|
|
|
io.WriteCloser
|
|
|
|
}{sr, sw})
|
2016-06-13 19:37:38 +08:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-06-15 18:15:49 +08:00
|
|
|
go server.Serve()
|
2016-06-15 18:04:25 +08:00
|
|
|
client, err := NewClientPipe(cr, cw)
|
2016-06-13 19:37:38 +08:00
|
|
|
if err != nil {
|
2016-06-15 18:04:25 +08:00
|
|
|
t.Fatalf("%+v\n", err)
|
2016-06-13 19:37:38 +08:00
|
|
|
}
|
|
|
|
return client, server
|
|
|
|
}
|
|
|
|
|
|
|
|
type sshFxpTestBadExtendedPacket struct {
|
|
|
|
ID uint32
|
|
|
|
Extension string
|
|
|
|
Data string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p sshFxpTestBadExtendedPacket) id() uint32 { return p.ID }
|
|
|
|
|
|
|
|
func (p sshFxpTestBadExtendedPacket) MarshalBinary() ([]byte, error) {
|
|
|
|
l := 1 + 4 + 4 + // type(byte) + uint32 + uint32
|
|
|
|
len(p.Extension) +
|
|
|
|
len(p.Data)
|
|
|
|
|
|
|
|
b := make([]byte, 0, l)
|
|
|
|
b = append(b, ssh_FXP_EXTENDED)
|
|
|
|
b = marshalUint32(b, p.ID)
|
|
|
|
b = marshalString(b, p.Extension)
|
|
|
|
b = marshalString(b, p.Data)
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// test that errors are sent back when we request an invalid extended packet operation
|
|
|
|
func TestInvalidExtendedPacket(t *testing.T) {
|
2017-04-05 17:05:42 +08:00
|
|
|
client, server := clientServerPair(t)
|
2016-06-13 19:37:38 +08:00
|
|
|
defer client.Close()
|
2017-04-05 17:05:42 +08:00
|
|
|
defer server.Close()
|
|
|
|
|
2016-06-13 19:37:38 +08:00
|
|
|
badPacket := sshFxpTestBadExtendedPacket{client.nextID(), "thisDoesn'tExist", "foobar"}
|
2016-06-15 16:57:05 +08:00
|
|
|
_, _, err := client.clientConn.sendPacket(badPacket)
|
2016-06-13 20:40:12 +08:00
|
|
|
if err == nil {
|
2016-06-13 19:37:38 +08:00
|
|
|
t.Fatal("expected error from bad packet")
|
|
|
|
}
|
2016-06-15 18:04:25 +08:00
|
|
|
|
|
|
|
// try to stat a file; the client should have shut down.
|
|
|
|
filePath := "/etc/passwd"
|
|
|
|
_, err = client.Stat(filePath)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected error from closed connection")
|
|
|
|
}
|
|
|
|
|
2016-06-13 19:37:38 +08:00
|
|
|
}
|
2017-04-05 17:08:03 +08:00
|
|
|
|
|
|
|
// test that server handles concurrent requests correctly
|
|
|
|
func TestConcurrentRequests(t *testing.T) {
|
|
|
|
client, server := clientServerPair(t)
|
|
|
|
defer client.Close()
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
concurrency := 2
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(concurrency)
|
|
|
|
|
|
|
|
for i := 0; i < concurrency; i++ {
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
for j := 0; j < 1024; j++ {
|
|
|
|
f, err := client.Open("/etc/passwd")
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("failed to open file: %v", err)
|
|
|
|
}
|
|
|
|
if err := f.Close(); err != nil {
|
|
|
|
t.Errorf("failed t close file: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
}
|
2017-08-10 01:43:14 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
The format of the `longname' field is unspecified by this protocol.
|
|
|
|
It MUST be suitable for use in the output of a directory listing
|
|
|
|
command (in fact, the recommended operation for a directory listing
|
|
|
|
command is to simply display this data). However, clients SHOULD NOT
|
|
|
|
attempt to parse the longname field for file attributes; they SHOULD
|
|
|
|
use the attrs field instead.
|
|
|
|
|
|
|
|
The recommended format for the longname field is as follows:
|
|
|
|
|
|
|
|
-rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer
|
|
|
|
1234567890 123 12345678 12345678 12345678 123456789012
|
|
|
|
|
|
|
|
Here, the first line is sample output, and the second field indicates
|
|
|
|
widths of the various fields. Fields are separated by spaces. The
|
|
|
|
first field lists file permissions for user, group, and others; the
|
|
|
|
second field is link count; the third field is the name of the user
|
|
|
|
who owns the file; the fourth field is the name of the group that
|
|
|
|
owns the file; the fifth field is the size of the file in bytes; the
|
|
|
|
sixth field (which actually may contain spaces, but is fixed to 12
|
|
|
|
characters) is the file modification time, and the seventh field is
|
|
|
|
the file name. Each field is specified to be a minimum of certain
|
|
|
|
number of character positions (indicated by the second line above),
|
|
|
|
but may also be longer if the data does not fit in the specified
|
|
|
|
length.
|
|
|
|
|
|
|
|
The SSH_FXP_ATTRS response has the following format:
|
|
|
|
|
|
|
|
uint32 id
|
|
|
|
ATTRS attrs
|
|
|
|
|
|
|
|
where `id' is the request identifier, and `attrs' is the returned
|
|
|
|
file attributes as described in Section ``File Attributes''.
|
|
|
|
*/
|
|
|
|
func TestRunLs(t *testing.T) {
|
|
|
|
|
|
|
|
}
|