mirror of https://github.com/pkg/sftp.git
				
				
				
			
		
			
				
	
	
		
			508 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			508 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
| package sftp
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding"
 | |
| 	"errors"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"testing"
 | |
| )
 | |
| 
 | |
| func TestMarshalUint32(t *testing.T) {
 | |
| 	var tests = []struct {
 | |
| 		v    uint32
 | |
| 		want []byte
 | |
| 	}{
 | |
| 		{0, []byte{0, 0, 0, 0}},
 | |
| 		{42, []byte{0, 0, 0, 42}},
 | |
| 		{42 << 8, []byte{0, 0, 42, 0}},
 | |
| 		{42 << 16, []byte{0, 42, 0, 0}},
 | |
| 		{42 << 24, []byte{42, 0, 0, 0}},
 | |
| 		{^uint32(0), []byte{255, 255, 255, 255}},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		got := marshalUint32(nil, tt.v)
 | |
| 		if !bytes.Equal(tt.want, got) {
 | |
| 			t.Errorf("marshalUint32(%d) = %#v, want %#v", tt.v, got, tt.want)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestMarshalUint64(t *testing.T) {
 | |
| 	var tests = []struct {
 | |
| 		v    uint64
 | |
| 		want []byte
 | |
| 	}{
 | |
| 		{0, []byte{0, 0, 0, 0, 0, 0, 0, 0}},
 | |
| 		{42, []byte{0, 0, 0, 0, 0, 0, 0, 42}},
 | |
| 		{42 << 8, []byte{0, 0, 0, 0, 0, 0, 42, 0}},
 | |
| 		{42 << 16, []byte{0, 0, 0, 0, 0, 42, 0, 0}},
 | |
| 		{42 << 24, []byte{0, 0, 0, 0, 42, 0, 0, 0}},
 | |
| 		{42 << 32, []byte{0, 0, 0, 42, 0, 0, 0, 0}},
 | |
| 		{42 << 40, []byte{0, 0, 42, 0, 0, 0, 0, 0}},
 | |
| 		{42 << 48, []byte{0, 42, 0, 0, 0, 0, 0, 0}},
 | |
| 		{42 << 56, []byte{42, 0, 0, 0, 0, 0, 0, 0}},
 | |
| 		{^uint64(0), []byte{255, 255, 255, 255, 255, 255, 255, 255}},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		got := marshalUint64(nil, tt.v)
 | |
| 		if !bytes.Equal(tt.want, got) {
 | |
| 			t.Errorf("marshalUint64(%d) = %#v, want %#v", tt.v, got, tt.want)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestMarshalString(t *testing.T) {
 | |
| 	var tests = []struct {
 | |
| 		v    string
 | |
| 		want []byte
 | |
| 	}{
 | |
| 		{"", []byte{0, 0, 0, 0}},
 | |
| 		{"/", []byte{0x0, 0x0, 0x0, 0x01, '/'}},
 | |
| 		{"/foo", []byte{0x0, 0x0, 0x0, 0x4, '/', 'f', 'o', 'o'}},
 | |
| 		{"\x00bar", []byte{0x0, 0x0, 0x0, 0x4, 0, 'b', 'a', 'r'}},
 | |
| 		{"b\x00ar", []byte{0x0, 0x0, 0x0, 0x4, 'b', 0, 'a', 'r'}},
 | |
| 		{"ba\x00r", []byte{0x0, 0x0, 0x0, 0x4, 'b', 'a', 0, 'r'}},
 | |
| 		{"bar\x00", []byte{0x0, 0x0, 0x0, 0x4, 'b', 'a', 'r', 0}},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		got := marshalString(nil, tt.v)
 | |
| 		if !bytes.Equal(tt.want, got) {
 | |
| 			t.Errorf("marshalString(%q) = %#v, want %#v", tt.v, got, tt.want)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestMarshal(t *testing.T) {
 | |
| 	type Struct struct {
 | |
| 		X, Y, Z uint32
 | |
| 	}
 | |
| 
 | |
| 	var tests = []struct {
 | |
| 		v    interface{}
 | |
| 		want []byte
 | |
| 	}{
 | |
| 		{uint8(42), []byte{42}},
 | |
| 		{uint32(42 << 8), []byte{0, 0, 42, 0}},
 | |
| 		{uint64(42 << 32), []byte{0, 0, 0, 42, 0, 0, 0, 0}},
 | |
| 		{"foo", []byte{0x0, 0x0, 0x0, 0x3, 'f', 'o', 'o'}},
 | |
| 		{Struct{1, 2, 3}, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3}},
 | |
| 		{[]uint32{1, 2, 3}, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3}},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		got := marshal(nil, tt.v)
 | |
| 		if !bytes.Equal(tt.want, got) {
 | |
| 			t.Errorf("marshal(%#v) = %#v, want %#v", tt.v, got, tt.want)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestUnmarshalUint32(t *testing.T) {
 | |
| 	testBuffer := []byte{
 | |
| 		0, 0, 0, 0,
 | |
| 		0, 0, 0, 42,
 | |
| 		0, 0, 42, 0,
 | |
| 		0, 42, 0, 0,
 | |
| 		42, 0, 0, 0,
 | |
| 		255, 0, 0, 254,
 | |
| 	}
 | |
| 
 | |
| 	var wants = []uint32{
 | |
| 		0,
 | |
| 		42,
 | |
| 		42 << 8,
 | |
| 		42 << 16,
 | |
| 		42 << 24,
 | |
| 		255<<24 | 254,
 | |
| 	}
 | |
| 
 | |
| 	var i int
 | |
| 	for len(testBuffer) > 0 {
 | |
| 		got, rest := unmarshalUint32(testBuffer)
 | |
| 
 | |
| 		if got != wants[i] {
 | |
| 			t.Fatalf("unmarshalUint32(%#v) = %d, want %d", testBuffer[:4], got, wants[i])
 | |
| 		}
 | |
| 
 | |
| 		i++
 | |
| 		testBuffer = rest
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestUnmarshalUint64(t *testing.T) {
 | |
| 	testBuffer := []byte{
 | |
| 		0, 0, 0, 0, 0, 0, 0, 0,
 | |
| 		0, 0, 0, 0, 0, 0, 0, 42,
 | |
| 		0, 0, 0, 0, 0, 0, 42, 0,
 | |
| 		0, 0, 0, 0, 0, 42, 0, 0,
 | |
| 		0, 0, 0, 0, 42, 0, 0, 0,
 | |
| 		0, 0, 0, 42, 0, 0, 0, 0,
 | |
| 		0, 0, 42, 0, 0, 0, 0, 0,
 | |
| 		0, 42, 0, 0, 0, 0, 0, 0,
 | |
| 		42, 0, 0, 0, 0, 0, 0, 0,
 | |
| 		255, 0, 0, 0, 0, 0, 0, 254,
 | |
| 	}
 | |
| 
 | |
| 	var wants = []uint64{
 | |
| 		0,
 | |
| 		42,
 | |
| 		42 << 8,
 | |
| 		42 << 16,
 | |
| 		42 << 24,
 | |
| 		42 << 32,
 | |
| 		42 << 40,
 | |
| 		42 << 48,
 | |
| 		42 << 56,
 | |
| 		255<<56 | 254,
 | |
| 	}
 | |
| 
 | |
| 	var i int
 | |
| 	for len(testBuffer) > 0 {
 | |
| 		got, rest := unmarshalUint64(testBuffer)
 | |
| 
 | |
| 		if got != wants[i] {
 | |
| 			t.Fatalf("unmarshalUint64(%#v) = %d, want %d", testBuffer[:8], got, wants[i])
 | |
| 		}
 | |
| 
 | |
| 		i++
 | |
| 		testBuffer = rest
 | |
| 	}
 | |
| }
 | |
| 
 | |
| var unmarshalStringTests = []struct {
 | |
| 	b    []byte
 | |
| 	want string
 | |
| 	rest []byte
 | |
| }{
 | |
| 	{marshalString(nil, ""), "", nil},
 | |
| 	{marshalString(nil, "blah"), "blah", nil},
 | |
| }
 | |
| 
 | |
| func TestUnmarshalString(t *testing.T) {
 | |
| 	testBuffer := []byte{
 | |
| 		0, 0, 0, 0,
 | |
| 		0, 0, 0, 1, '/',
 | |
| 		0, 0, 0, 4, '/', 'f', 'o', 'o',
 | |
| 		0, 0, 0, 4, 0, 'b', 'a', 'r',
 | |
| 		0, 0, 0, 4, 'b', 0, 'a', 'r',
 | |
| 		0, 0, 0, 4, 'b', 'a', 0, 'r',
 | |
| 		0, 0, 0, 4, 'b', 'a', 'r', 0,
 | |
| 	}
 | |
| 
 | |
| 	var wants = []string{
 | |
| 		"",
 | |
| 		"/",
 | |
| 		"/foo",
 | |
| 		"\x00bar",
 | |
| 		"b\x00ar",
 | |
| 		"ba\x00r",
 | |
| 		"bar\x00",
 | |
| 	}
 | |
| 
 | |
| 	var i int
 | |
| 	for len(testBuffer) > 0 {
 | |
| 		got, rest := unmarshalString(testBuffer)
 | |
| 
 | |
| 		if got != wants[i] {
 | |
| 			t.Fatalf("unmarshalUint64(%#v...) = %q, want %q", testBuffer[:4], got, wants[i])
 | |
| 		}
 | |
| 
 | |
| 		i++
 | |
| 		testBuffer = rest
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestSendPacket(t *testing.T) {
 | |
| 	var tests = []struct {
 | |
| 		packet encoding.BinaryMarshaler
 | |
| 		want   []byte
 | |
| 	}{
 | |
| 		{
 | |
| 			packet: &sshFxInitPacket{
 | |
| 				Version: 3,
 | |
| 				Extensions: []extensionPair{
 | |
| 					{"posix-rename@openssh.com", "1"},
 | |
| 				},
 | |
| 			},
 | |
| 			want: []byte{
 | |
| 				0x0, 0x0, 0x0, 0x26,
 | |
| 				0x1,
 | |
| 				0x0, 0x0, 0x0, 0x3,
 | |
| 				0x0, 0x0, 0x0, 0x18,
 | |
| 				'p', 'o', 's', 'i', 'x', '-', 'r', 'e', 'n', 'a', 'm', 'e', '@', 'o', 'p', 'e', 'n', 's', 's', 'h', '.', 'c', 'o', 'm',
 | |
| 				0x0, 0x0, 0x0, 0x1,
 | |
| 				'1',
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			packet: &sshFxpOpenPacket{
 | |
| 				ID:     1,
 | |
| 				Path:   "/foo",
 | |
| 				Pflags: flags(os.O_RDONLY),
 | |
| 			},
 | |
| 			want: []byte{
 | |
| 				0x0, 0x0, 0x0, 0x15,
 | |
| 				0x3,
 | |
| 				0x0, 0x0, 0x0, 0x1,
 | |
| 				0x0, 0x0, 0x0, 0x4, '/', 'f', 'o', 'o',
 | |
| 				0x0, 0x0, 0x0, 0x1,
 | |
| 				0x0, 0x0, 0x0, 0x0,
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			packet: &sshFxpWritePacket{
 | |
| 				ID:     124,
 | |
| 				Handle: "foo",
 | |
| 				Offset: 13,
 | |
| 				Length: uint32(len("bar")),
 | |
| 				Data:   []byte("bar"),
 | |
| 			},
 | |
| 			want: []byte{
 | |
| 				0x0, 0x0, 0x0, 0x1b,
 | |
| 				0x6,
 | |
| 				0x0, 0x0, 0x0, 0x7c,
 | |
| 				0x0, 0x0, 0x0, 0x3, 'f', 'o', 'o',
 | |
| 				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd,
 | |
| 				0x0, 0x0, 0x0, 0x3, 'b', 'a', 'r',
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			packet: &sshFxpSetstatPacket{
 | |
| 				ID:    31,
 | |
| 				Path:  "/bar",
 | |
| 				Flags: sshFileXferAttrUIDGID,
 | |
| 				Attrs: struct {
 | |
| 					UID uint32
 | |
| 					GID uint32
 | |
| 				}{
 | |
| 					UID: 1000,
 | |
| 					GID: 100,
 | |
| 				},
 | |
| 			},
 | |
| 			want: []byte{
 | |
| 				0x0, 0x0, 0x0, 0x19,
 | |
| 				0x9,
 | |
| 				0x0, 0x0, 0x0, 0x1f,
 | |
| 				0x0, 0x0, 0x0, 0x4, '/', 'b', 'a', 'r',
 | |
| 				0x0, 0x0, 0x0, 0x2,
 | |
| 				0x0, 0x0, 0x3, 0xe8,
 | |
| 				0x0, 0x0, 0x0, 0x64,
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		b := new(bytes.Buffer)
 | |
| 		sendPacket(b, tt.packet)
 | |
| 		if got := b.Bytes(); !bytes.Equal(tt.want, got) {
 | |
| 			t.Errorf("sendPacket(%v): got %x want %x", tt.packet, tt.want, got)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func sp(data encoding.BinaryMarshaler) []byte {
 | |
| 	b := new(bytes.Buffer)
 | |
| 	sendPacket(b, data)
 | |
| 	return b.Bytes()
 | |
| }
 | |
| 
 | |
| func TestRecvPacket(t *testing.T) {
 | |
| 	var recvPacketTests = []struct {
 | |
| 		b []byte
 | |
| 
 | |
| 		want    uint8
 | |
| 		body    []byte
 | |
| 		wantErr error
 | |
| 	}{
 | |
| 		{
 | |
| 			b: sp(&sshFxInitPacket{
 | |
| 				Version: 3,
 | |
| 				Extensions: []extensionPair{
 | |
| 					{"posix-rename@openssh.com", "1"},
 | |
| 				},
 | |
| 			}),
 | |
| 			want: sshFxpInit,
 | |
| 			body: []byte{
 | |
| 				0x0, 0x0, 0x0, 0x3,
 | |
| 				0x0, 0x0, 0x0, 0x18,
 | |
| 				'p', 'o', 's', 'i', 'x', '-', 'r', 'e', 'n', 'a', 'm', 'e', '@', 'o', 'p', 'e', 'n', 's', 's', 'h', '.', 'c', 'o', 'm',
 | |
| 				0x0, 0x0, 0x0, 0x01,
 | |
| 				'1',
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			b: []byte{
 | |
| 				0x0, 0x0, 0x0, 0x0,
 | |
| 			},
 | |
| 			wantErr: errShortPacket,
 | |
| 		},
 | |
| 		{
 | |
| 			b: []byte{
 | |
| 				0xff, 0xff, 0xff, 0xff,
 | |
| 			},
 | |
| 			wantErr: errLongPacket,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range recvPacketTests {
 | |
| 		r := bytes.NewReader(tt.b)
 | |
| 
 | |
| 		got, body, err := recvPacket(r, nil, 0)
 | |
| 		if tt.wantErr == nil {
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("recvPacket(%#v): unexpected error: %v", tt.b, err)
 | |
| 			}
 | |
| 		} else {
 | |
| 			if !errors.Is(err, tt.wantErr) {
 | |
| 				t.Fatalf("recvPacket(%#v) = %v, want %v", tt.b, err, tt.wantErr)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if got != tt.want {
 | |
| 			t.Errorf("recvPacket(%#v) = %#v, want %#v", tt.b, got, tt.want)
 | |
| 		}
 | |
| 
 | |
| 		if !bytes.Equal(body, tt.body) {
 | |
| 			t.Errorf("recvPacket(%#v) = %#v, want %#v", tt.b, body, tt.body)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestSSHFxpOpenPacketreadonly(t *testing.T) {
 | |
| 	var tests = []struct {
 | |
| 		pflags uint32
 | |
| 		ok     bool
 | |
| 	}{
 | |
| 		{
 | |
| 			pflags: sshFxfRead,
 | |
| 			ok:     true,
 | |
| 		},
 | |
| 		{
 | |
| 			pflags: sshFxfWrite,
 | |
| 			ok:     false,
 | |
| 		},
 | |
| 		{
 | |
| 			pflags: sshFxfRead | sshFxfWrite,
 | |
| 			ok:     false,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		p := &sshFxpOpenPacket{
 | |
| 			Pflags: tt.pflags,
 | |
| 		}
 | |
| 
 | |
| 		if want, got := tt.ok, p.readonly(); want != got {
 | |
| 			t.Errorf("unexpected value for p.readonly(): want: %v, got: %v",
 | |
| 				want, got)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestSSHFxpOpenPackethasPflags(t *testing.T) {
 | |
| 	var tests = []struct {
 | |
| 		desc      string
 | |
| 		haveFlags uint32
 | |
| 		testFlags []uint32
 | |
| 		ok        bool
 | |
| 	}{
 | |
| 		{
 | |
| 			desc:      "have read, test against write",
 | |
| 			haveFlags: sshFxfRead,
 | |
| 			testFlags: []uint32{sshFxfWrite},
 | |
| 			ok:        false,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:      "have write, test against read",
 | |
| 			haveFlags: sshFxfWrite,
 | |
| 			testFlags: []uint32{sshFxfRead},
 | |
| 			ok:        false,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:      "have read+write, test against read",
 | |
| 			haveFlags: sshFxfRead | sshFxfWrite,
 | |
| 			testFlags: []uint32{sshFxfRead},
 | |
| 			ok:        true,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:      "have read+write, test against write",
 | |
| 			haveFlags: sshFxfRead | sshFxfWrite,
 | |
| 			testFlags: []uint32{sshFxfWrite},
 | |
| 			ok:        true,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:      "have read+write, test against read+write",
 | |
| 			haveFlags: sshFxfRead | sshFxfWrite,
 | |
| 			testFlags: []uint32{sshFxfRead, sshFxfWrite},
 | |
| 			ok:        true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range tests {
 | |
| 		t.Log(tt.desc)
 | |
| 
 | |
| 		p := &sshFxpOpenPacket{
 | |
| 			Pflags: tt.haveFlags,
 | |
| 		}
 | |
| 
 | |
| 		if want, got := tt.ok, p.hasPflags(tt.testFlags...); want != got {
 | |
| 			t.Errorf("unexpected value for p.hasPflags(%#v): want: %v, got: %v",
 | |
| 				tt.testFlags, want, got)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func benchMarshal(b *testing.B, packet encoding.BinaryMarshaler) {
 | |
| 	b.ResetTimer()
 | |
| 
 | |
| 	for i := 0; i < b.N; i++ {
 | |
| 		sendPacket(ioutil.Discard, packet)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func BenchmarkMarshalInit(b *testing.B) {
 | |
| 	benchMarshal(b, &sshFxInitPacket{
 | |
| 		Version: 3,
 | |
| 		Extensions: []extensionPair{
 | |
| 			{"posix-rename@openssh.com", "1"},
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func BenchmarkMarshalOpen(b *testing.B) {
 | |
| 	benchMarshal(b, &sshFxpOpenPacket{
 | |
| 		ID:     1,
 | |
| 		Path:   "/home/test/some/random/path",
 | |
| 		Pflags: flags(os.O_RDONLY),
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func BenchmarkMarshalWriteWorstCase(b *testing.B) {
 | |
| 	data := make([]byte, 32*1024)
 | |
| 
 | |
| 	benchMarshal(b, &sshFxpWritePacket{
 | |
| 		ID:     1,
 | |
| 		Handle: "someopaquehandle",
 | |
| 		Offset: 0,
 | |
| 		Length: uint32(len(data)),
 | |
| 		Data:   data,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func BenchmarkMarshalWrite1k(b *testing.B) {
 | |
| 	data := make([]byte, 1025)
 | |
| 
 | |
| 	benchMarshal(b, &sshFxpWritePacket{
 | |
| 		ID:     1,
 | |
| 		Handle: "someopaquehandle",
 | |
| 		Offset: 0,
 | |
| 		Length: uint32(len(data)),
 | |
| 		Data:   data,
 | |
| 	})
 | |
| }
 |