| 
									
										
										
										
											2013-11-06 12:40:35 +08:00
										 |  |  | package sftp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // sftp integration tests
 | 
					
						
							|  |  |  | // enable with -integration
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2013-11-15 18:47:28 +08:00
										 |  |  | 	"crypto/sha1" | 
					
						
							| 
									
										
										
										
											2013-11-06 12:40:35 +08:00
										 |  |  | 	"flag" | 
					
						
							| 
									
										
										
										
											2013-11-08 19:11:02 +08:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2013-11-06 12:40:35 +08:00
										 |  |  | 	"io/ioutil" | 
					
						
							| 
									
										
										
										
											2014-06-12 03:17:53 +08:00
										 |  |  | 	"math/rand" | 
					
						
							| 
									
										
										
										
											2013-11-06 12:40:35 +08:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"os/exec" | 
					
						
							| 
									
										
										
										
											2013-11-08 18:00:26 +08:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2013-11-08 18:24:50 +08:00
										 |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2014-06-12 03:17:53 +08:00
										 |  |  | 	"reflect" | 
					
						
							| 
									
										
										
										
											2013-11-06 12:40:35 +08:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2014-06-12 03:17:53 +08:00
										 |  |  | 	"testing/quick" | 
					
						
							| 
									
										
										
										
											2013-11-08 18:24:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-11 09:57:03 +08:00
										 |  |  | 	"github.com/kr/fs" | 
					
						
							| 
									
										
										
										
											2013-11-06 12:40:35 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-06 16:29:59 +08:00
										 |  |  | const ( | 
					
						
							|  |  |  | 	READONLY  = true | 
					
						
							|  |  |  | 	READWRITE = false | 
					
						
							| 
									
										
										
										
											2013-11-08 18:00:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-08 18:24:50 +08:00
										 |  |  | 	debuglevel = "ERROR" // set to "DEBUG" for debugging
 | 
					
						
							| 
									
										
										
										
											2013-11-06 16:29:59 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-06 12:40:35 +08:00
										 |  |  | var testIntegration = flag.Bool("integration", false, "perform integration tests against sftp server process") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // testClient returns a *Client connected to a localy running sftp-server
 | 
					
						
							|  |  |  | // the *exec.Cmd returned must be defer Wait'd.
 | 
					
						
							| 
									
										
										
										
											2013-11-06 16:29:59 +08:00
										 |  |  | func testClient(t *testing.T, readonly bool) (*Client, *exec.Cmd) { | 
					
						
							| 
									
										
										
										
											2013-11-08 18:00:26 +08:00
										 |  |  | 	if !*testIntegration { | 
					
						
							|  |  |  | 		t.Skip("skipping intergration test") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	cmd := exec.Command("/usr/lib/openssh/sftp-server", "-e", "-R", "-l", debuglevel) // log to stderr, read only
 | 
					
						
							| 
									
										
										
										
											2013-11-06 16:29:59 +08:00
										 |  |  | 	if !readonly { | 
					
						
							| 
									
										
										
										
											2013-11-08 18:00:26 +08:00
										 |  |  | 		cmd = exec.Command("/usr/lib/openssh/sftp-server", "-e", "-l", debuglevel) // log to stderr
 | 
					
						
							| 
									
										
										
										
											2013-11-06 16:29:59 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2013-11-06 12:40:35 +08:00
										 |  |  | 	cmd.Stderr = os.Stdout | 
					
						
							|  |  |  | 	pw, err := cmd.StdinPipe() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	pr, err := cmd.StdoutPipe() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := cmd.Start(); err != nil { | 
					
						
							|  |  |  | 		t.Skipf("could not start sftp-server process: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	sftp := &Client{ | 
					
						
							|  |  |  | 		w: pw, | 
					
						
							|  |  |  | 		r: pr, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := sftp.sendInit(); err != nil { | 
					
						
							|  |  |  | 		defer cmd.Wait() | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := sftp.recvVersion(); err != nil { | 
					
						
							|  |  |  | 		defer cmd.Wait() | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return sftp, cmd | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestNewClient(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2013-11-06 16:29:59 +08:00
										 |  |  | 	sftp, cmd := testClient(t, READONLY) | 
					
						
							| 
									
										
										
										
											2013-11-06 12:40:35 +08:00
										 |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := sftp.Close(); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestClientLstat(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2013-11-06 16:29:59 +08:00
										 |  |  | 	sftp, cmd := testClient(t, READONLY) | 
					
						
							| 
									
										
										
										
											2013-11-06 12:40:35 +08:00
										 |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := ioutil.TempFile("", "sftptest") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer os.Remove(f.Name()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	want, err := os.Lstat(f.Name()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	got, err := sftp.Lstat(f.Name()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !sameFile(want, got) { | 
					
						
							|  |  |  | 		t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), want, got) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-08 19:11:02 +08:00
										 |  |  | func TestClientLstatMissing(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2013-11-06 16:29:59 +08:00
										 |  |  | 	sftp, cmd := testClient(t, READONLY) | 
					
						
							|  |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := ioutil.TempFile("", "sftptest") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	os.Remove(f.Name()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_, err = sftp.Lstat(f.Name()) | 
					
						
							|  |  |  | 	if err1, ok := err.(*StatusError); !ok || err1.Code != ssh_FX_NO_SUCH_FILE { | 
					
						
							|  |  |  | 		t.Fatalf("Lstat: want: %v, got %#v", ssh_FX_NO_SUCH_FILE, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-06 12:53:14 +08:00
										 |  |  | func TestClientOpen(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2013-11-06 16:29:59 +08:00
										 |  |  | 	sftp, cmd := testClient(t, READONLY) | 
					
						
							| 
									
										
										
										
											2013-11-06 12:53:14 +08:00
										 |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := ioutil.TempFile("", "sftptest") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer os.Remove(f.Name()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	got, err := sftp.Open(f.Name()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := got.Close(); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-06-12 03:17:53 +08:00
										 |  |  | const seekBytes = 128 * 1024 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type seek struct { | 
					
						
							|  |  |  | 	offset int64 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s seek) Generate(r *rand.Rand, _ int) reflect.Value { | 
					
						
							|  |  |  | 	s.offset = int64(r.Int31n(seekBytes)) | 
					
						
							|  |  |  | 	return reflect.ValueOf(s) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-06-13 16:22:04 +08:00
										 |  |  | func (s seek) set(t *testing.T, r io.ReadSeeker) { | 
					
						
							|  |  |  | 	if _, err := r.Seek(s.offset, os.SEEK_SET); err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("error while seeking with %+v: %v", s, err) | 
					
						
							| 
									
										
										
										
											2014-06-12 03:17:53 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-06-13 16:22:04 +08:00
										 |  |  | func (s seek) current(t *testing.T, r io.ReadSeeker) { | 
					
						
							| 
									
										
										
										
											2014-06-12 03:17:53 +08:00
										 |  |  | 	const mid = seekBytes / 2 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-06-13 16:22:04 +08:00
										 |  |  | 	skip := s.offset / 2 | 
					
						
							|  |  |  | 	if s.offset > mid { | 
					
						
							| 
									
										
										
										
											2014-06-12 03:17:53 +08:00
										 |  |  | 		skip = -skip | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-06-13 16:22:04 +08:00
										 |  |  | 	if _, err := r.Seek(mid, os.SEEK_SET); err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("error seeking to midpoint with %+v: %v", s, err) | 
					
						
							| 
									
										
										
										
											2014-06-12 03:17:53 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2014-06-13 16:22:04 +08:00
										 |  |  | 	if _, err := r.Seek(skip, os.SEEK_CUR); err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("error seeking from %d with %+v: %v", mid, s, err) | 
					
						
							| 
									
										
										
										
											2014-06-12 03:17:53 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-06-13 16:22:04 +08:00
										 |  |  | func (s seek) end(t *testing.T, r io.ReadSeeker) { | 
					
						
							|  |  |  | 	if _, err := r.Seek(-s.offset, os.SEEK_END); err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("error seeking from end with %+v: %v", s, err) | 
					
						
							| 
									
										
										
										
											2014-06-12 03:17:53 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestClientSeek(t *testing.T) { | 
					
						
							|  |  |  | 	sftp, cmd := testClient(t, READONLY) | 
					
						
							|  |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	fOS, err := ioutil.TempFile("", "seek-test") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer fOS.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	fSFTP, err := sftp.Open(fOS.Name()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer fSFTP.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	writeN(t, fOS, seekBytes) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := quick.CheckEqual( | 
					
						
							|  |  |  | 		func(s seek) (string, int64) { s.set(t, fOS); return readHash(t, fOS) }, | 
					
						
							|  |  |  | 		func(s seek) (string, int64) { s.set(t, fSFTP); return readHash(t, fSFTP) }, | 
					
						
							|  |  |  | 		nil, | 
					
						
							|  |  |  | 	); err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Seek: expected equal absolute seeks: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := quick.CheckEqual( | 
					
						
							|  |  |  | 		func(s seek) (string, int64) { s.current(t, fOS); return readHash(t, fOS) }, | 
					
						
							|  |  |  | 		func(s seek) (string, int64) { s.current(t, fSFTP); return readHash(t, fSFTP) }, | 
					
						
							|  |  |  | 		nil, | 
					
						
							|  |  |  | 	); err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Seek: expected equal seeks from middle: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := quick.CheckEqual( | 
					
						
							|  |  |  | 		func(s seek) (string, int64) { s.end(t, fOS); return readHash(t, fOS) }, | 
					
						
							|  |  |  | 		func(s seek) (string, int64) { s.end(t, fSFTP); return readHash(t, fSFTP) }, | 
					
						
							|  |  |  | 		nil, | 
					
						
							|  |  |  | 	); err != nil { | 
					
						
							|  |  |  | 		t.Errorf("Seek: expected equal seeks from end: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2013-11-08 19:11:02 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-06 16:29:59 +08:00
										 |  |  | func TestClientCreate(t *testing.T) { | 
					
						
							|  |  |  | 	sftp, cmd := testClient(t, READWRITE) | 
					
						
							|  |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := ioutil.TempFile("", "sftptest") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer f.Close() | 
					
						
							|  |  |  | 	defer os.Remove(f.Name()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f2, err := sftp.Create(f.Name()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer f2.Close() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-14 12:32:21 +08:00
										 |  |  | func TestClientAppend(t *testing.T) { | 
					
						
							|  |  |  | 	sftp, cmd := testClient(t, READWRITE) | 
					
						
							|  |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := ioutil.TempFile("", "sftptest") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer f.Close() | 
					
						
							|  |  |  | 	defer os.Remove(f.Name()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f2, err := sftp.OpenFile(f.Name(), os.O_RDWR|os.O_APPEND) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer f2.Close() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-06 16:29:59 +08:00
										 |  |  | func TestClientCreateFailed(t *testing.T) { | 
					
						
							|  |  |  | 	sftp, cmd := testClient(t, READONLY) | 
					
						
							|  |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := ioutil.TempFile("", "sftptest") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer f.Close() | 
					
						
							|  |  |  | 	defer os.Remove(f.Name()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f2, err := sftp.Create(f.Name()) | 
					
						
							|  |  |  | 	if err1, ok := err.(*StatusError); !ok || err1.Code != ssh_FX_PERMISSION_DENIED { | 
					
						
							|  |  |  | 		t.Fatalf("Create: want: %v, got %#v", ssh_FX_PERMISSION_DENIED, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err == nil { | 
					
						
							|  |  |  | 		f2.Close() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-06 13:03:08 +08:00
										 |  |  | func TestClientFileStat(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2013-11-06 16:29:59 +08:00
										 |  |  | 	sftp, cmd := testClient(t, READONLY) | 
					
						
							| 
									
										
										
										
											2013-11-06 13:03:08 +08:00
										 |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := ioutil.TempFile("", "sftptest") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer os.Remove(f.Name()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	want, err := os.Lstat(f.Name()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f2, err := sftp.Open(f.Name()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	got, err := f2.Stat() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !sameFile(want, got) { | 
					
						
							|  |  |  | 		t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), want, got) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-08 18:56:25 +08:00
										 |  |  | func TestClientRemove(t *testing.T) { | 
					
						
							|  |  |  | 	sftp, cmd := testClient(t, READWRITE) | 
					
						
							|  |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := ioutil.TempFile("", "sftptest") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := sftp.Remove(f.Name()); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestClientRemoveFailed(t *testing.T) { | 
					
						
							|  |  |  | 	sftp, cmd := testClient(t, READONLY) | 
					
						
							|  |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := ioutil.TempFile("", "sftptest") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := sftp.Remove(f.Name()); err == nil { | 
					
						
							|  |  |  | 		t.Fatalf("Remove(%v): want: permission denied, got %v", f.Name(), err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if _, err := os.Lstat(f.Name()); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-08 19:11:02 +08:00
										 |  |  | func TestClientRename(t *testing.T) { | 
					
						
							|  |  |  | 	sftp, cmd := testClient(t, READWRITE) | 
					
						
							|  |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := ioutil.TempFile("", "sftptest") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	f2 := f.Name() + ".new" | 
					
						
							|  |  |  | 	if err := sftp.Rename(f.Name(), f2); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if _, err := os.Lstat(f2); err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-06 12:40:35 +08:00
										 |  |  | func sameFile(want, got os.FileInfo) bool { | 
					
						
							|  |  |  | 	return want.Name() == got.Name() && | 
					
						
							|  |  |  | 		want.Size() == got.Size() | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2013-11-08 18:00:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-15 18:17:11 +08:00
										 |  |  | var clientReadTests = []struct { | 
					
						
							|  |  |  | 	n int64 | 
					
						
							|  |  |  | }{ | 
					
						
							|  |  |  | 	{0}, | 
					
						
							|  |  |  | 	{1}, | 
					
						
							|  |  |  | 	{1000}, | 
					
						
							|  |  |  | 	{1024}, | 
					
						
							|  |  |  | 	{1025}, | 
					
						
							|  |  |  | 	{2048}, | 
					
						
							|  |  |  | 	{4096}, | 
					
						
							|  |  |  | 	{1 << 12}, | 
					
						
							|  |  |  | 	{1 << 13}, | 
					
						
							|  |  |  | 	{1 << 14}, | 
					
						
							|  |  |  | 	{1 << 15}, | 
					
						
							|  |  |  | 	{1 << 16}, | 
					
						
							|  |  |  | 	{1 << 17}, | 
					
						
							|  |  |  | 	{1 << 18}, | 
					
						
							|  |  |  | 	{1 << 19}, | 
					
						
							|  |  |  | 	{1 << 20}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestClientRead(t *testing.T) { | 
					
						
							|  |  |  | 	sftp, cmd := testClient(t, READONLY) | 
					
						
							|  |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	d, err := ioutil.TempDir("", "sftptest") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer os.RemoveAll(d) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, tt := range clientReadTests { | 
					
						
							|  |  |  | 		f, err := ioutil.TempFile(d, "read-test") | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		defer f.Close() | 
					
						
							| 
									
										
										
										
											2013-11-15 18:47:28 +08:00
										 |  |  | 		hash := writeN(t, f, tt.n) | 
					
						
							| 
									
										
										
										
											2013-11-15 18:17:11 +08:00
										 |  |  | 		f2, err := sftp.Open(f.Name()) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		defer f2.Close() | 
					
						
							| 
									
										
										
										
											2013-11-15 18:47:28 +08:00
										 |  |  | 		hash2, n := readHash(t, f2) | 
					
						
							|  |  |  | 		if hash != hash2 || tt.n != n { | 
					
						
							|  |  |  | 			t.Errorf("Read: hash: want: %q, got %q, read: want: %v, got %v", hash, hash2, tt.n, n) | 
					
						
							| 
									
										
										
										
											2013-11-15 18:17:11 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-15 18:47:28 +08:00
										 |  |  | // readHash reads r until EOF returning the number of bytes read
 | 
					
						
							|  |  |  | // and the hash of the contents.
 | 
					
						
							|  |  |  | func readHash(t *testing.T, r io.Reader) (string, int64) { | 
					
						
							|  |  |  | 	h := sha1.New() | 
					
						
							|  |  |  | 	tr := io.TeeReader(r, h) | 
					
						
							|  |  |  | 	read, err := io.Copy(ioutil.Discard, tr) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return string(h.Sum(nil)), read | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // writeN writes n bytes of random data to w and returns the
 | 
					
						
							|  |  |  | // hash of that data.
 | 
					
						
							|  |  |  | func writeN(t *testing.T, w io.Writer, n int64) string { | 
					
						
							|  |  |  | 	rand, err := os.Open("/dev/urandom") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer rand.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	h := sha1.New() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mw := io.MultiWriter(w, h) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	written, err := io.CopyN(mw, rand, n) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if written != n { | 
					
						
							|  |  |  | 		t.Fatalf("CopyN(%v): wrote: %v", n, written) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return string(h.Sum(nil)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-11-08 18:00:26 +08:00
										 |  |  | var clientWriteTests = []struct { | 
					
						
							|  |  |  | 	n     int | 
					
						
							|  |  |  | 	total int64 // cumulative file size
 | 
					
						
							|  |  |  | }{ | 
					
						
							|  |  |  | 	{0, 0}, | 
					
						
							|  |  |  | 	{1, 1}, | 
					
						
							|  |  |  | 	{0, 1}, | 
					
						
							|  |  |  | 	{999, 1000}, | 
					
						
							|  |  |  | 	{24, 1024}, | 
					
						
							|  |  |  | 	{1023, 2047}, | 
					
						
							|  |  |  | 	{2048, 4095}, | 
					
						
							|  |  |  | 	{1 << 12, 8191}, | 
					
						
							|  |  |  | 	{1 << 13, 16383}, | 
					
						
							|  |  |  | 	{1 << 14, 32767}, | 
					
						
							|  |  |  | 	{1 << 15, 65535}, | 
					
						
							|  |  |  | 	{1 << 16, 131071}, | 
					
						
							|  |  |  | 	{1 << 17, 262143}, | 
					
						
							| 
									
										
										
										
											2013-11-08 18:24:50 +08:00
										 |  |  | 	{1 << 18, 524287}, | 
					
						
							|  |  |  | 	{1 << 19, 1048575}, | 
					
						
							|  |  |  | 	{1 << 20, 2097151}, | 
					
						
							|  |  |  | 	{1 << 21, 4194303}, | 
					
						
							| 
									
										
										
										
											2013-11-08 18:00:26 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestClientWrite(t *testing.T) { | 
					
						
							|  |  |  | 	sftp, cmd := testClient(t, READWRITE) | 
					
						
							|  |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	d, err := ioutil.TempDir("", "sftptest") | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer os.RemoveAll(d) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f := path.Join(d, "writeTest") | 
					
						
							|  |  |  | 	w, err := sftp.Create(f) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer w.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, tt := range clientWriteTests { | 
					
						
							|  |  |  | 		got, err := w.Write(make([]byte, tt.n)) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if got != tt.n { | 
					
						
							|  |  |  | 			t.Errorf("Write(%v): wrote: want: %v, got %v", tt.n, tt.n, got) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		fi, err := os.Stat(f) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if total := fi.Size(); total != tt.total { | 
					
						
							|  |  |  | 			t.Errorf("Write(%v): size: want: %v, got %v", tt.n, tt.total, total) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2013-11-08 18:24:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // taken from github.com/kr/fs/walk_test.go
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type PathTest struct { | 
					
						
							|  |  |  | 	path, result string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Node struct { | 
					
						
							|  |  |  | 	name    string | 
					
						
							|  |  |  | 	entries []*Node // nil if the entry is a file
 | 
					
						
							|  |  |  | 	mark    int | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var tree = &Node{ | 
					
						
							|  |  |  | 	"testdata", | 
					
						
							|  |  |  | 	[]*Node{ | 
					
						
							|  |  |  | 		{"a", nil, 0}, | 
					
						
							|  |  |  | 		{"b", []*Node{}, 0}, | 
					
						
							|  |  |  | 		{"c", nil, 0}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"d", | 
					
						
							|  |  |  | 			[]*Node{ | 
					
						
							|  |  |  | 				{"x", nil, 0}, | 
					
						
							|  |  |  | 				{"y", []*Node{}, 0}, | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					"z", | 
					
						
							|  |  |  | 					[]*Node{ | 
					
						
							|  |  |  | 						{"u", nil, 0}, | 
					
						
							|  |  |  | 						{"v", nil, 0}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 					0, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			0, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	0, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func walkTree(n *Node, path string, f func(path string, n *Node)) { | 
					
						
							|  |  |  | 	f(path, n) | 
					
						
							|  |  |  | 	for _, e := range n.entries { | 
					
						
							|  |  |  | 		walkTree(e, filepath.Join(path, e.name), f) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func makeTree(t *testing.T) { | 
					
						
							|  |  |  | 	walkTree(tree, tree.name, func(path string, n *Node) { | 
					
						
							|  |  |  | 		if n.entries == nil { | 
					
						
							|  |  |  | 			fd, err := os.Create(path) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Errorf("makeTree: %v", err) | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			fd.Close() | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			os.Mkdir(path, 0770) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func checkMarks(t *testing.T, report bool) { | 
					
						
							|  |  |  | 	walkTree(tree, tree.name, func(path string, n *Node) { | 
					
						
							|  |  |  | 		if n.mark != 1 && report { | 
					
						
							|  |  |  | 			t.Errorf("node %s mark = %d; expected 1", path, n.mark) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		n.mark = 0 | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Assumes that each node name is unique. Good enough for a test.
 | 
					
						
							|  |  |  | // If clear is true, any incoming error is cleared before return. The errors
 | 
					
						
							|  |  |  | // are always accumulated, though.
 | 
					
						
							|  |  |  | func mark(path string, info os.FileInfo, err error, errors *[]error, clear bool) error { | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		*errors = append(*errors, err) | 
					
						
							|  |  |  | 		if clear { | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	name := info.Name() | 
					
						
							|  |  |  | 	walkTree(tree, tree.name, func(path string, n *Node) { | 
					
						
							|  |  |  | 		if n.name == name { | 
					
						
							|  |  |  | 			n.mark++ | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestClientWalk(t *testing.T) { | 
					
						
							|  |  |  | 	sftp, cmd := testClient(t, READONLY) | 
					
						
							|  |  |  | 	defer cmd.Wait() | 
					
						
							|  |  |  | 	defer sftp.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	makeTree(t) | 
					
						
							|  |  |  | 	errors := make([]error, 0, 10) | 
					
						
							|  |  |  | 	clear := true | 
					
						
							|  |  |  | 	markFn := func(walker *fs.Walker) (err error) { | 
					
						
							|  |  |  | 		for walker.Step() { | 
					
						
							|  |  |  | 			err = mark(walker.Path(), walker.Stat(), walker.Err(), &errors, clear) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Expect no errors.
 | 
					
						
							|  |  |  | 	err := markFn(sftp.Walk(tree.name)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("no error expected, found: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if len(errors) != 0 { | 
					
						
							|  |  |  | 		t.Fatalf("unexpected errors: %s", errors) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	checkMarks(t, true) | 
					
						
							|  |  |  | 	errors = errors[0:0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test permission errors.  Only possible if we're not root
 | 
					
						
							|  |  |  | 	// and only on some file systems (AFS, FAT).  To avoid errors during
 | 
					
						
							|  |  |  | 	// all.bash on those file systems, skip during go test -short.
 | 
					
						
							|  |  |  | 	if os.Getuid() > 0 && !testing.Short() { | 
					
						
							|  |  |  | 		// introduce 2 errors: chmod top-level directories to 0
 | 
					
						
							|  |  |  | 		os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0) | 
					
						
							|  |  |  | 		os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// 3) capture errors, expect two.
 | 
					
						
							|  |  |  | 		// mark respective subtrees manually
 | 
					
						
							|  |  |  | 		markTree(tree.entries[1]) | 
					
						
							|  |  |  | 		markTree(tree.entries[3]) | 
					
						
							|  |  |  | 		// correct double-marking of directory itself
 | 
					
						
							|  |  |  | 		tree.entries[1].mark-- | 
					
						
							|  |  |  | 		tree.entries[3].mark-- | 
					
						
							|  |  |  | 		err := markFn(sftp.Walk(tree.name)) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatalf("expected no error return from Walk, got %s", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if len(errors) != 2 { | 
					
						
							|  |  |  | 			t.Errorf("expected 2 errors, got %d: %s", len(errors), errors) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// the inaccessible subtrees were marked manually
 | 
					
						
							|  |  |  | 		checkMarks(t, true) | 
					
						
							|  |  |  | 		errors = errors[0:0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// 4) capture errors, stop after first error.
 | 
					
						
							|  |  |  | 		// mark respective subtrees manually
 | 
					
						
							|  |  |  | 		markTree(tree.entries[1]) | 
					
						
							|  |  |  | 		markTree(tree.entries[3]) | 
					
						
							|  |  |  | 		// correct double-marking of directory itself
 | 
					
						
							|  |  |  | 		tree.entries[1].mark-- | 
					
						
							|  |  |  | 		tree.entries[3].mark-- | 
					
						
							|  |  |  | 		clear = false // error will stop processing
 | 
					
						
							|  |  |  | 		err = markFn(sftp.Walk(tree.name)) | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			t.Fatalf("expected error return from Walk") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if len(errors) != 1 { | 
					
						
							|  |  |  | 			t.Errorf("expected 1 error, got %d: %s", len(errors), errors) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// the inaccessible subtrees were marked manually
 | 
					
						
							|  |  |  | 		checkMarks(t, false) | 
					
						
							|  |  |  | 		errors = errors[0:0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// restore permissions
 | 
					
						
							|  |  |  | 		os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770) | 
					
						
							|  |  |  | 		os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// cleanup
 | 
					
						
							|  |  |  | 	if err := os.RemoveAll(tree.name); err != nil { | 
					
						
							|  |  |  | 		t.Errorf("removeTree: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |