vendor: update all dependencies to latest versions

This commit is contained in:
Nick Craig-Wood 2018-01-16 13:20:59 +00:00
parent 8e83fb6fb9
commit 7d3a17725d
4878 changed files with 1974229 additions and 201215 deletions

View file

@ -1,10 +1,11 @@
language: go
go_import_path: github.com/pkg/sftp
# current and previous stable releases, and tip
# current and previous stable releases, plus tip
# remember to exclude previous and tip for macs below
go:
- 1.7.x
- 1.8.x
- 1.9.x
- tip
os:
@ -14,7 +15,7 @@ os:
matrix:
exclude:
- os: osx
go: 1.7.x
go: 1.8.x
- os: osx
go: tip

View file

@ -94,6 +94,10 @@ func fileStatFromInfo(fi os.FileInfo) (uint32, FileStat) {
func unmarshalAttrs(b []byte) (*FileStat, []byte) {
flags, b := unmarshalUint32(b)
return getFileStat(flags, b)
}
func getFileStat(flags uint32, b []byte) (*FileStat, []byte) {
var fs FileStat
if flags&ssh_FILEXFER_ATTR_SIZE == ssh_FILEXFER_ATTR_SIZE {
fs.Size, b = unmarshalUint64(b)

26
vendor/github.com/pkg/sftp/client.go generated vendored
View file

@ -15,8 +15,10 @@ import (
)
// InternalInconsistency indicates the packets sent and the data queued to be
// written to the file don't match up. It is an unusual error and if you get it
// you should file a ticket.
// written to the file don't match up. It is an unusual error and usually is
// caused by bad behavior server side or connection issues. The error is
// limited in scope to the call where it happened, the client object is still
// OK to use as long as the connection is still open.
var InternalInconsistency = errors.New("internal inconsistency")
// A ClientOption is a function which applies configuration to a Client.
@ -575,6 +577,26 @@ func (c *Client) Rename(oldname, newname string) error {
}
}
// PosixRename renames a file using the posix-rename@openssh.com extension
// which will replace newname if it already exists.
func (c *Client) PosixRename(oldname, newname string) error {
id := c.nextID()
typ, data, err := c.sendPacket(sshFxpPosixRenamePacket{
ID: id,
Oldpath: oldname,
Newpath: newname,
})
if err != nil {
return err
}
switch typ {
case ssh_FXP_STATUS:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
}
}
func (c *Client) realpath(path string) (string, error) {
id := c.nextID()
typ, data, err := c.sendPacket(sshFxpRealpathPacket{

View file

@ -627,6 +627,27 @@ func TestClientRename(t *testing.T) {
}
}
func TestClientPosixRename(t *testing.T) {
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
defer cmd.Wait()
defer sftp.Close()
f, err := ioutil.TempFile("", "sftptest")
if err != nil {
t.Fatal(err)
}
f2 := f.Name() + ".new"
if err := sftp.PosixRename(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)
}
}
func TestClientGetwd(t *testing.T) {
sftp, cmd := testClient(t, READONLY, NO_DELAY)
defer cmd.Wait()

View file

@ -1,7 +1,9 @@
package sftp_test
import (
"bufio"
"fmt"
"io"
"log"
"os"
"os/exec"
@ -107,13 +109,13 @@ func ExampleClient_Mkdir_parents() {
sshFxFailure := uint32(4)
mkdirParents := func(client *sftp.Client, dir string) (err error) {
var parents string
if path.IsAbs(dir) {
// Otherwise, an absolute path given below would be turned in to a relative one
// by splitting on "/"
parents = "/"
}
for _, name := range strings.Split(dir, "/") {
if name == "" {
// Paths with double-/ in them should just move along
@ -145,3 +147,18 @@ func ExampleClient_Mkdir_parents() {
log.Fatal(err)
}
}
func ExampleFile_ReadFrom_bufio() {
// Using Bufio to buffer writes going to an sftp.File won't buffer as it
// skips buffering if the underlying writer support ReadFrom. The
// workaround is to wrap your writer in a struct that only implements
// io.Writer.
//
// For background see github.com/pkg/sftp/issues/125
var data_source io.Reader
var f *sftp.File
type writerOnly struct{ io.Writer }
bw := bufio.NewWriter(writerOnly{f}) // no ReadFrom()
bw.ReadFrom(data_source)
}

View file

@ -30,11 +30,6 @@ type hasHandle interface {
getHandle() string
}
type isOpener interface {
hasPath
isOpener()
}
type notReadOnly interface {
notReadOnly()
}
@ -52,12 +47,10 @@ func (p sshFxpStatvfsPacket) getPath() string { return p.Path }
func (p sshFxpRemovePacket) getPath() string { return p.Filename }
func (p sshFxpRenamePacket) getPath() string { return p.Oldpath }
func (p sshFxpSymlinkPacket) getPath() string { return p.Targetpath }
func (p sshFxpOpendirPacket) getPath() string { return p.Path }
func (p sshFxpOpenPacket) getPath() string { return p.Path }
// Openers implement hasPath and isOpener
func (p sshFxpOpendirPacket) getPath() string { return p.Path }
func (p sshFxpOpendirPacket) isOpener() {}
func (p sshFxpOpenPacket) getPath() string { return p.Path }
func (p sshFxpOpenPacket) isOpener() {}
func (p sshFxpExtendedPacketPosixRename) getPath() string { return p.Oldpath }
// hasHandle
func (p sshFxpFstatPacket) getHandle() string { return p.Handle }
@ -67,14 +60,15 @@ func (p sshFxpWritePacket) getHandle() string { return p.Handle }
func (p sshFxpReaddirPacket) getHandle() string { return p.Handle }
// notReadOnly
func (p sshFxpWritePacket) notReadOnly() {}
func (p sshFxpSetstatPacket) notReadOnly() {}
func (p sshFxpFsetstatPacket) notReadOnly() {}
func (p sshFxpRemovePacket) notReadOnly() {}
func (p sshFxpMkdirPacket) notReadOnly() {}
func (p sshFxpRmdirPacket) notReadOnly() {}
func (p sshFxpRenamePacket) notReadOnly() {}
func (p sshFxpSymlinkPacket) notReadOnly() {}
func (p sshFxpWritePacket) notReadOnly() {}
func (p sshFxpSetstatPacket) notReadOnly() {}
func (p sshFxpFsetstatPacket) notReadOnly() {}
func (p sshFxpRemovePacket) notReadOnly() {}
func (p sshFxpMkdirPacket) notReadOnly() {}
func (p sshFxpRmdirPacket) notReadOnly() {}
func (p sshFxpRenamePacket) notReadOnly() {}
func (p sshFxpSymlinkPacket) notReadOnly() {}
func (p sshFxpExtendedPacketPosixRename) notReadOnly() {}
// this has a handle, but is only used for close
func (p sshFxpClosePacket) getHandle() string { return p.Handle }

54
vendor/github.com/pkg/sftp/packet.go generated vendored
View file

@ -586,6 +586,30 @@ func (p *sshFxpRenamePacket) UnmarshalBinary(b []byte) error {
return nil
}
type sshFxpPosixRenamePacket struct {
ID uint32
Oldpath string
Newpath string
}
func (p sshFxpPosixRenamePacket) id() uint32 { return p.ID }
func (p sshFxpPosixRenamePacket) MarshalBinary() ([]byte, error) {
const ext = "posix-rename@openssh.com"
l := 1 + 4 + // type(byte) + uint32
4 + len(ext) +
4 + len(p.Oldpath) +
4 + len(p.Newpath)
b := make([]byte, 0, l)
b = append(b, ssh_FXP_EXTENDED)
b = marshalUint32(b, p.ID)
b = marshalString(b, ext)
b = marshalString(b, p.Oldpath)
b = marshalString(b, p.Newpath)
return b, nil
}
type sshFxpWritePacket struct {
ID uint32
Handle string
@ -870,6 +894,8 @@ func (p *sshFxpExtendedPacket) UnmarshalBinary(b []byte) error {
switch p.ExtendedRequest {
case "statvfs@openssh.com":
p.SpecificPacket = &sshFxpExtendedPacketStatVFS{}
case "posix-rename@openssh.com":
p.SpecificPacket = &sshFxpExtendedPacketPosixRename{}
default:
return errUnknownExtendedPacket
}
@ -896,3 +922,31 @@ func (p *sshFxpExtendedPacketStatVFS) UnmarshalBinary(b []byte) error {
}
return nil
}
type sshFxpExtendedPacketPosixRename struct {
ID uint32
ExtendedRequest string
Oldpath string
Newpath string
}
func (p sshFxpExtendedPacketPosixRename) id() uint32 { return p.ID }
func (p sshFxpExtendedPacketPosixRename) readonly() bool { return false }
func (p *sshFxpExtendedPacketPosixRename) UnmarshalBinary(b []byte) error {
var err error
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
return err
} else if p.ExtendedRequest, b, err = unmarshalStringSafe(b); err != nil {
return err
} else if p.Oldpath, b, err = unmarshalStringSafe(b); err != nil {
return err
} else if p.Newpath, b, err = unmarshalStringSafe(b); err != nil {
return err
}
return nil
}
func (p sshFxpExtendedPacketPosixRename) respond(s *Server) error {
err := os.Rename(p.Oldpath, p.Newpath)
return s.sendError(p, err)
}

63
vendor/github.com/pkg/sftp/request-attrs.go generated vendored Normal file
View file

@ -0,0 +1,63 @@
package sftp
// Methods on the Request object to make working with the Flags bitmasks and
// Attr(ibutes) byte blob easier. Use Pflags() when working with an Open/Write
// request and AttrFlags() and Attributes() when working with SetStat requests.
import "os"
// Open packet pflags
type pflags struct {
Read, Write, Append, Creat, Trunc, Excl bool
}
// testable constructor
func newPflags(flags uint32) pflags {
return pflags{
Read: flags&ssh_FXF_READ != 0,
Write: flags&ssh_FXF_WRITE != 0,
Append: flags&ssh_FXF_APPEND != 0,
Creat: flags&ssh_FXF_CREAT != 0,
Trunc: flags&ssh_FXF_TRUNC != 0,
Excl: flags&ssh_FXF_EXCL != 0,
}
}
// Check bitmap/uint32 for Open packet pflag values
func (r *Request) Pflags() pflags {
return newPflags(r.Flags)
}
// File attribute flags
type aflags struct {
Size, UidGid, Permissions, Acmodtime bool
}
// testable constructor
func newAflags(flags uint32) aflags {
return aflags{
Size: (flags & ssh_FILEXFER_ATTR_SIZE) != 0,
UidGid: (flags & ssh_FILEXFER_ATTR_UIDGID) != 0,
Permissions: (flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0,
Acmodtime: (flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0,
}
}
// Check bitmap/uint32 for file attribute flags
func (r *Request) AttrFlags(flags uint32) aflags {
return newAflags(r.Flags)
}
// File attributes
type fileattrs FileStat
// Return Mode wrapped in os.FileMode
func (a fileattrs) FileMode() os.FileMode {
return os.FileMode(a.Mode)
}
// Parse file attributes byte blob and return them in object
func (r *Request) Attributes() fileattrs {
fa, _ := getFileStat(r.Flags, r.Attrs)
return fileattrs(*fa)
}

51
vendor/github.com/pkg/sftp/request-attrs_test.go generated vendored Normal file
View file

@ -0,0 +1,51 @@
package sftp
import (
"os"
"github.com/stretchr/testify/assert"
"testing"
)
func TestRequestPflags(t *testing.T) {
pflags := newPflags(ssh_FXF_READ | ssh_FXF_WRITE | ssh_FXF_APPEND)
assert.True(t, pflags.Read)
assert.True(t, pflags.Write)
assert.True(t, pflags.Append)
assert.False(t, pflags.Creat)
assert.False(t, pflags.Trunc)
assert.False(t, pflags.Excl)
}
func TestRequestAflags(t *testing.T) {
aflags := newAflags(ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_UIDGID)
assert.True(t, aflags.Size)
assert.True(t, aflags.UidGid)
assert.False(t, aflags.Acmodtime)
assert.False(t, aflags.Permissions)
}
func TestRequestAttributes(t *testing.T) {
// UID/GID
fa := fileattrs{UID: 1, GID: 2}
fl := uint32(ssh_FILEXFER_ATTR_UIDGID)
at := []byte{}
at = marshalUint32(at, 1)
at = marshalUint32(at, 2)
test_fs, _ := getFileStat(fl, at)
assert.Equal(t, fa, fileattrs(*test_fs))
// Size and Mode
fa = fileattrs{Mode: 700, Size: 99}
fl = uint32(ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_PERMISSIONS)
at = []byte{}
at = marshalUint64(at, 99)
at = marshalUint32(at, 700)
test_fs, _ = getFileStat(fl, at)
test_fa := fileattrs(*test_fs)
assert.Equal(t, fa, test_fa)
// FileMode
assert.True(t, test_fa.FileMode().IsRegular())
assert.False(t, test_fa.FileMode().IsDir())
assert.Equal(t, test_fa.FileMode().Perm(), os.FileMode(700).Perm())
}

View file

@ -24,8 +24,19 @@ func InMemHandler() Handlers {
return Handlers{root, root, root, root}
}
// So I can test Handlers returning errors
var (
readErr error = nil
writeErr error = nil
cmdErr error = nil
listErr error = nil
)
// Handlers
func (fs *root) Fileread(r *Request) (io.ReaderAt, error) {
if readErr != nil {
return nil, readErr
}
fs.filesLock.Lock()
defer fs.filesLock.Unlock()
file, err := fs.fetch(r.Filepath)
@ -42,6 +53,9 @@ func (fs *root) Fileread(r *Request) (io.ReaderAt, error) {
}
func (fs *root) Filewrite(r *Request) (io.WriterAt, error) {
if writeErr != nil {
return nil, writeErr
}
fs.filesLock.Lock()
defer fs.filesLock.Unlock()
file, err := fs.fetch(r.Filepath)
@ -60,6 +74,9 @@ func (fs *root) Filewrite(r *Request) (io.WriterAt, error) {
}
func (fs *root) Filecmd(r *Request) error {
if cmdErr != nil {
return cmdErr
}
fs.filesLock.Lock()
defer fs.filesLock.Unlock()
switch r.Method {
@ -116,6 +133,9 @@ func (f listerat) ListAt(ls []os.FileInfo, offset int64) (int, error) {
}
func (fs *root) Filelist(r *Request) (ListerAt, error) {
if listErr != nil {
return nil, listErr
}
fs.filesLock.Lock()
defer fs.filesLock.Unlock()

View file

@ -11,7 +11,8 @@ manner.
The Request structure has 5 public fields which you will deal with.
- Method (string) - string name of incoming call
- Filepath (string) - path of file to act on
- Filepath (string) - POSIX path of file to act on
- Flags (uint32) - 32bit bitmask value of file open/create flags
- Attrs ([]byte) - byte string of file attribute data
- Target (string) - target path for renames and sym-links
@ -25,13 +26,18 @@ then sends to the client.
### Filewrite(*Request) (io.Writer, error)
Handler for "Put" method and returns an io.Writer for the file which the server
then writes the uploaded file to.
then writes the uploaded file to. The file opening "pflags" are currently
preserved in the Request.Flags field as a 32bit bitmask value. See the [SFTP
spec](https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.3) for
details.
### Filecmd(*Request) error
Handles "SetStat", "Rename", "Rmdir", "Mkdir" and "Symlink" methods. Makes the
appropriate changes and returns nil for success or an filesystem like error
(eg. os.ErrNotExist).
(eg. os.ErrNotExist). The attributes are currently propagated in their raw form
([]byte) and will need to be unmarshalled to be useful. See the respond method
on sshFxpSetstatPacket for example of you might want to do this.
### Fileinfo(*Request) ([]os.FileInfo, error)
@ -44,5 +50,4 @@ Readlink).
- Add support for API users to see trace/debugging info of what is going on
inside SFTP server.
- Consider adding support for SFTP file append only mode.
- Unmarshal the file attributes into a structure on the Request object.

View file

@ -3,9 +3,9 @@ package sftp
import (
"encoding"
"io"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
@ -29,7 +29,7 @@ type RequestServer struct {
*serverConn
Handlers Handlers
pktMgr *packetManager
openRequests map[string]Request
openRequests map[string]*Request
openRequestLock sync.RWMutex
handleCount int
}
@ -47,35 +47,54 @@ func NewRequestServer(rwc io.ReadWriteCloser, h Handlers) *RequestServer {
serverConn: svrConn,
Handlers: h,
pktMgr: newPktMgr(svrConn),
openRequests: make(map[string]Request),
openRequests: make(map[string]*Request),
}
}
// Note that we are explicitly saving the Request as a value.
// New Open packet/Request
func (rs *RequestServer) nextRequest(r *Request) string {
rs.openRequestLock.Lock()
defer rs.openRequestLock.Unlock()
rs.handleCount++
handle := strconv.Itoa(rs.handleCount)
rs.openRequests[handle] = *r
rs.openRequests[handle] = r
return handle
}
// Returns pointer to new copy of Request object
func (rs *RequestServer) getRequest(handle string) (*Request, bool) {
// Returns Request from openRequests, bool is false if it is missing
// If the method is different, save/return a new Request w/ that Method.
//
// The Requests in openRequests work essentially as open file descriptors that
// you can do different things with. What you are doing with it are denoted by
// the first packet of that type (read/write/etc). We create a new Request when
// it changes to set the request.Method attribute in a thread safe way.
func (rs *RequestServer) getRequest(handle, method string) (*Request, bool) {
rs.openRequestLock.RLock()
defer rs.openRequestLock.RUnlock()
r, ok := rs.openRequests[handle]
return &r, ok
rs.openRequestLock.RUnlock()
if !ok || r.Method == method {
return r, ok
}
// if we make it here we need to replace the request
rs.openRequestLock.Lock()
defer rs.openRequestLock.Unlock()
r, ok = rs.openRequests[handle]
if !ok || r.Method == method { // re-check needed b/c lock race
return r, ok
}
r = &Request{Method: method, Filepath: r.Filepath, state: r.state}
rs.openRequests[handle] = r
return r, ok
}
func (rs *RequestServer) closeRequest(handle string) {
func (rs *RequestServer) closeRequest(handle string) error {
rs.openRequestLock.Lock()
defer rs.openRequestLock.Unlock()
if r, ok := rs.openRequests[handle]; ok {
r.close()
delete(rs.openRequests, handle)
return r.close()
}
return syscall.EBADF
}
// Close the read/write/closer to trigger exiting the main server loop
@ -129,43 +148,26 @@ func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
rpkt = sshFxVersionPacket{sftpProtocolVersion, nil}
case *sshFxpClosePacket:
handle := pkt.getHandle()
rs.closeRequest(handle)
rpkt = statusFromError(pkt, nil)
rpkt = statusFromError(pkt, rs.closeRequest(handle))
case *sshFxpRealpathPacket:
rpkt = cleanPacketPath(pkt)
case isOpener:
handle := rs.nextRequest(requestFromPacket(pkt))
case *sshFxpOpendirPacket:
request := requestFromPacket(pkt)
handle := rs.nextRequest(request)
rpkt = sshFxpHandlePacket{pkt.id(), handle}
case *sshFxpFstatPacket:
handle := pkt.getHandle()
request, ok := rs.getRequest(handle)
if !ok {
rpkt = statusFromError(pkt, syscall.EBADF)
} else {
request = requestFromPacket(
&sshFxpStatPacket{ID: pkt.id(), Path: request.Filepath})
rpkt = request.call(rs.Handlers, pkt)
}
case *sshFxpFsetstatPacket:
handle := pkt.getHandle()
request, ok := rs.getRequest(handle)
if !ok {
rpkt = statusFromError(pkt, syscall.EBADF)
} else {
request = requestFromPacket(
&sshFxpSetstatPacket{ID: pkt.id(), Path: request.Filepath,
Flags: pkt.Flags, Attrs: pkt.Attrs,
})
rpkt = request.call(rs.Handlers, pkt)
case *sshFxpOpenPacket:
request := requestFromPacket(pkt)
handle := rs.nextRequest(request)
rpkt = sshFxpHandlePacket{pkt.id(), handle}
if pkt.hasPflags(ssh_FXF_CREAT) {
if p := request.call(rs.Handlers, pkt); !isOk(p) {
rpkt = p // if error in write, return it
}
}
case hasHandle:
handle := pkt.getHandle()
request, ok := rs.getRequest(handle)
uerr := request.updateMethod(pkt)
if !ok || uerr != nil {
if uerr == nil {
uerr = syscall.EBADF
}
request, ok := rs.getRequest(handle, requestMethod(pkt))
if !ok {
rpkt = statusFromError(pkt, syscall.EBADF)
} else {
rpkt = request.call(rs.Handlers, pkt)
@ -185,6 +187,13 @@ func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
return nil
}
// True is responsePacket is an OK status packet
func isOk(rpkt responsePacket) bool {
p, ok := rpkt.(sshFxpStatusPacket)
return ok && p.StatusError.Code == ssh_FX_OK
}
// clean and return name packet for file
func cleanPacketPath(pkt *sshFxpRealpathPacket) responsePacket {
path := cleanPath(pkt.getPath())
return &sshFxpNamePacket{
@ -197,12 +206,13 @@ func cleanPacketPath(pkt *sshFxpRealpathPacket) responsePacket {
}
}
func cleanPath(path string) string {
cleanSlashPath := filepath.ToSlash(filepath.Clean(path))
if !strings.HasPrefix(cleanSlashPath, "/") {
return "/" + cleanSlashPath
// Makes sure we have a clean POSIX (/) absolute path to work with
func cleanPath(p string) string {
p = filepath.ToSlash(p)
if !filepath.IsAbs(p) {
p = "/" + p
}
return cleanSlashPath
return path.Clean(p)
}
// Wrap underlying connection methods to use packetManager

View file

@ -82,10 +82,10 @@ func TestRequestCache(t *testing.T) {
fh := p.svr.nextRequest(foo)
bh := p.svr.nextRequest(bar)
assert.Len(t, p.svr.openRequests, 2)
_foo, ok := p.svr.getRequest(fh)
_foo, ok := p.svr.getRequest(fh, "")
assert.Equal(t, foo, _foo)
assert.True(t, ok)
_, ok = p.svr.getRequest("zed")
_, ok = p.svr.getRequest("zed", "")
assert.False(t, ok)
p.svr.closeRequest(fh)
p.svr.closeRequest(bh)
@ -127,16 +127,36 @@ func TestRequestWrite(t *testing.T) {
assert.Equal(t, f.content, []byte("hello"))
}
// needs fail check
func TestRequestWriteEmpty(t *testing.T) {
p := clientRequestServerPair(t)
defer p.Close()
n, err := putTestFile(p.cli, "/foo", "")
assert.NoError(t, err)
assert.Equal(t, 0, n)
r := p.testHandler()
f, err := r.fetch("/foo")
if assert.Nil(t, err) {
assert.False(t, f.isdir)
assert.Equal(t, f.content, []byte(""))
}
// lets test with an error
writeErr = os.ErrInvalid
n, err = putTestFile(p.cli, "/bar", "")
assert.Error(t, err)
writeErr = nil
}
func TestRequestFilename(t *testing.T) {
p := clientRequestServerPair(t)
defer p.Close()
_, err := putTestFile(p.cli, "/foo", "hello")
assert.Nil(t, err)
assert.NoError(t, err)
r := p.testHandler()
f, err := r.fetch("/foo")
assert.Nil(t, err)
assert.NoError(t, err)
assert.Equal(t, f.Name(), "foo")
f, err = r.fetch("/bar")
assert.Error(t, err)
}
func TestRequestRead(t *testing.T) {
@ -266,11 +286,12 @@ func TestRequestFstat(t *testing.T) {
fp, err := p.cli.Open("/foo")
assert.Nil(t, err)
fi, err := fp.Stat()
assert.Nil(t, err)
assert.Equal(t, fi.Name(), "foo")
assert.Equal(t, fi.Size(), int64(5))
assert.Equal(t, fi.Mode(), os.FileMode(0644))
assert.NoError(t, testOsSys(fi.Sys()))
if assert.NoError(t, err) {
assert.Equal(t, fi.Name(), "foo")
assert.Equal(t, fi.Size(), int64(5))
assert.Equal(t, fi.Mode(), os.FileMode(0644))
assert.NoError(t, testOsSys(fi.Sys()))
}
}
func TestRequestStatFail(t *testing.T) {
@ -331,17 +352,25 @@ func TestRequestReaddir(t *testing.T) {
func TestCleanPath(t *testing.T) {
assert.Equal(t, "/", cleanPath("/"))
assert.Equal(t, "/", cleanPath("."))
assert.Equal(t, "/", cleanPath("/."))
assert.Equal(t, "/", cleanPath("/a/.."))
assert.Equal(t, "/a/c", cleanPath("/a/b/../c"))
assert.Equal(t, "/a/c", cleanPath("/a/b/../c/"))
assert.Equal(t, "/a", cleanPath("/a/b/.."))
assert.Equal(t, "/a/b/c", cleanPath("/a/b/c"))
assert.Equal(t, "/", cleanPath("//"))
assert.Equal(t, "/a", cleanPath("/a/"))
assert.Equal(t, "/a", cleanPath("a/"))
assert.Equal(t, "/a/b/c", cleanPath("/a//b//c/"))
// filepath.ToSlash does not touch \ as char on unix systems, so os.PathSeparator is used for windows compatible tests
// filepath.ToSlash does not touch \ as char on unix systems
// so os.PathSeparator is used for windows compatible tests
bslash := string(os.PathSeparator)
assert.Equal(t, "/", cleanPath(bslash))
assert.Equal(t, "/", cleanPath(bslash+bslash))
assert.Equal(t, "/a", cleanPath(bslash+"a"+bslash))
assert.Equal(t, "/a", cleanPath("a"+bslash))
assert.Equal(t, "/a/b/c", cleanPath(bslash+"a"+bslash+bslash+"b"+bslash+bslash+"c"+bslash))
assert.Equal(t, "/a/b/c",
cleanPath(bslash+"a"+bslash+bslash+"b"+bslash+bslash+"c"+bslash))
}

179
vendor/github.com/pkg/sftp/request.go generated vendored
View file

@ -24,8 +24,8 @@ type Request struct {
Attrs []byte // convert to sub-struct
Target string // for renames and sym-links
// reader/writer/readdir from handlers
stateLock *sync.RWMutex
state *state
stateLock sync.RWMutex
state state
}
type state struct {
@ -35,22 +35,13 @@ type state struct {
lsoffset int64
}
type packet_data struct {
_id uint32
data []byte
length uint32
offset int64
}
func (pd packet_data) id() uint32 {
return pd._id
}
// New Request initialized based on packet data
func requestFromPacket(pkt hasPath) *Request {
method := requestMethod(pkt)
request := NewRequest(method, pkt.getPath())
switch p := pkt.(type) {
case *sshFxpOpenPacket:
request.Flags = p.Pflags
case *sshFxpSetstatPacket:
request.Flags = p.Flags
request.Attrs = p.Attrs.([]byte)
@ -62,16 +53,9 @@ func requestFromPacket(pkt hasPath) *Request {
return request
}
func newRequest() *Request {
return &Request{state: &state{}, stateLock: &sync.RWMutex{}}
}
// NewRequest creates a new Request object.
func NewRequest(method, path string) *Request {
request := newRequest()
request.Method = method
request.Filepath = cleanPath(path)
return request
return &Request{Method: method, Filepath: cleanPath(path)}
}
// Returns current offset for file list
@ -89,19 +73,20 @@ func (r *Request) lsInc(offset int64) {
}
// manage file read/write state
func (r *Request) setFileState(s interface{}) {
func (r *Request) setWriterState(wa io.WriterAt) {
r.stateLock.Lock()
defer r.stateLock.Unlock()
switch s := s.(type) {
case io.WriterAt:
r.state.writerAt = s
case io.ReaderAt:
r.state.readerAt = s
case ListerAt:
r.state.listerAt = s
case int64:
r.state.lsoffset = s
}
r.state.writerAt = wa
}
func (r *Request) setReaderState(ra io.ReaderAt) {
r.stateLock.Lock()
defer r.stateLock.Unlock()
r.state.readerAt = ra
}
func (r *Request) setListerState(la ListerAt) {
r.stateLock.Lock()
defer r.stateLock.Unlock()
r.state.listerAt = la
}
func (r *Request) getWriter() io.WriterAt {
@ -123,29 +108,29 @@ func (r *Request) getLister() ListerAt {
}
// Close reader/writer if possible
func (r *Request) close() {
func (r *Request) close() error {
rd := r.getReader()
if c, ok := rd.(io.Closer); ok {
c.Close()
return c.Close()
}
wt := r.getWriter()
if c, ok := wt.(io.Closer); ok {
c.Close()
return c.Close()
}
return nil
}
// called from worker to handle packet/request
func (r *Request) call(handlers Handlers, pkt requestPacket) responsePacket {
pd := packetData(pkt)
switch r.Method {
case "Get":
return fileget(handlers.FileGet, r, pd)
case "Put": // add "Append" to this to handle append only file writes
return fileput(handlers.FilePut, r, pd)
return fileget(handlers.FileGet, r, pkt)
case "Put", "Open":
return fileput(handlers.FilePut, r, pkt)
case "Setstat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove":
return filecmd(handlers.FileCmd, r, pd)
return filecmd(handlers.FileCmd, r, pkt)
case "List", "Stat", "Readlink":
return filelist(handlers.FileList, r, pd)
return filelist(handlers.FileList, r, pkt)
default:
return statusFromError(pkt,
errors.Errorf("unexpected method: %s", r.Method))
@ -153,91 +138,78 @@ func (r *Request) call(handlers Handlers, pkt requestPacket) responsePacket {
}
// file data for additional read/write packets
func packetData(p requestPacket) packet_data {
pd := packet_data{_id: p.id()}
func packetData(p requestPacket) (data []byte, offset int64, length uint32) {
switch p := p.(type) {
case *sshFxpReadPacket:
pd.length = p.Len
pd.offset = int64(p.Offset)
length = p.Len
offset = int64(p.Offset)
case *sshFxpWritePacket:
pd.data = p.Data
pd.length = p.Length
pd.offset = int64(p.Offset)
data = p.Data
length = p.Length
offset = int64(p.Offset)
}
return pd
return
}
// wrap FileReader handler
func fileget(h FileReader, r *Request, pd packet_data) responsePacket {
func fileget(h FileReader, r *Request, pkt requestPacket) responsePacket {
var err error
reader := r.getReader()
if reader == nil {
reader, err = h.Fileread(r)
if err != nil {
return statusFromError(pd, err)
return statusFromError(pkt, err)
}
r.setFileState(reader)
r.setReaderState(reader)
}
data := make([]byte, clamp(pd.length, maxTxPacket))
n, err := reader.ReadAt(data, pd.offset)
_, offset, length := packetData(pkt)
data := make([]byte, clamp(length, maxTxPacket))
n, err := reader.ReadAt(data, offset)
// only return EOF erro if no data left to read
if err != nil && (err != io.EOF || n == 0) {
return statusFromError(pd, err)
return statusFromError(pkt, err)
}
return &sshFxpDataPacket{
ID: pd.id(),
ID: pkt.id(),
Length: uint32(n),
Data: data[:n],
}
}
// wrap FileWriter handler
func fileput(h FileWriter, r *Request, pd packet_data) responsePacket {
func fileput(h FileWriter, r *Request, pkt requestPacket) responsePacket {
var err error
writer := r.getWriter()
if writer == nil {
writer, err = h.Filewrite(r)
if err != nil {
return statusFromError(pd, err)
return statusFromError(pkt, err)
}
r.setFileState(writer)
r.setWriterState(writer)
}
_, err = writer.WriteAt(pd.data, pd.offset)
if err != nil {
return statusFromError(pd, err)
}
return &sshFxpStatusPacket{
ID: pd.id(),
StatusError: StatusError{
Code: ssh_FX_OK,
}}
data, offset, _ := packetData(pkt)
_, err = writer.WriteAt(data, offset)
return statusFromError(pkt, err)
}
// wrap FileCmder handler
func filecmd(h FileCmder, r *Request, pd packet_data) responsePacket {
func filecmd(h FileCmder, r *Request, pkt requestPacket) responsePacket {
err := h.Filecmd(r)
if err != nil {
return statusFromError(pd, err)
}
return &sshFxpStatusPacket{
ID: pd.id(),
StatusError: StatusError{
Code: ssh_FX_OK,
}}
return statusFromError(pkt, err)
}
// wrap FileLister handler
func filelist(h FileLister, r *Request, pd packet_data) responsePacket {
func filelist(h FileLister, r *Request, pkt requestPacket) responsePacket {
var err error
lister := r.getLister()
if lister == nil {
lister, err = h.Filelist(r)
if err != nil {
return statusFromError(pd, err)
return statusFromError(pkt, err)
}
r.setFileState(lister)
r.setListerState(lister)
}
offset := r.lsNext()
@ -250,13 +222,13 @@ func filelist(h FileLister, r *Request, pd packet_data) responsePacket {
switch r.Method {
case "List":
if err != nil && err != io.EOF {
return statusFromError(pd, err)
return statusFromError(pkt, err)
}
if n == 0 {
return statusFromError(pd, io.EOF)
return statusFromError(pkt, io.EOF)
}
dirname := filepath.ToSlash(path.Base(r.Filepath))
ret := &sshFxpNamePacket{ID: pd.id()}
ret := &sshFxpNamePacket{ID: pkt.id()}
for _, fi := range finfo {
ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
@ -268,29 +240,29 @@ func filelist(h FileLister, r *Request, pd packet_data) responsePacket {
return ret
case "Stat":
if err != nil && err != io.EOF {
return statusFromError(pd, err)
return statusFromError(pkt, err)
}
if n == 0 {
err = &os.PathError{Op: "stat", Path: r.Filepath,
Err: syscall.ENOENT}
return statusFromError(pd, err)
return statusFromError(pkt, err)
}
return &sshFxpStatResponse{
ID: pd.id(),
ID: pkt.id(),
info: finfo[0],
}
case "Readlink":
if err != nil && err != io.EOF {
return statusFromError(pd, err)
return statusFromError(pkt, err)
}
if n == 0 {
err = &os.PathError{Op: "readlink", Path: r.Filepath,
Err: syscall.ENOENT}
return statusFromError(pd, err)
return statusFromError(pkt, err)
}
filename := finfo[0].Name()
return &sshFxpNamePacket{
ID: pd.id(),
ID: pkt.id(),
NameAttrs: []sshFxpNameAttr{{
Name: filename,
LongName: filename,
@ -299,31 +271,22 @@ func filelist(h FileLister, r *Request, pd packet_data) responsePacket {
}
default:
err = errors.Errorf("unexpected method: %s", r.Method)
return statusFromError(pd, err)
return statusFromError(pkt, err)
}
}
// file data for additional read/write packets
func (r *Request) updateMethod(p hasHandle) error {
switch p := p.(type) {
case *sshFxpReadPacket:
r.Method = "Get"
case *sshFxpWritePacket:
r.Method = "Put"
case *sshFxpReaddirPacket:
r.Method = "List"
default:
return errors.Errorf("unexpected packet type %T", p)
}
return nil
}
// init attributes of request object from packet data
func requestMethod(p hasPath) (method string) {
func requestMethod(p requestPacket) (method string) {
switch p.(type) {
case *sshFxpReadPacket:
method = "Get"
case *sshFxpWritePacket:
method = "Put"
case *sshFxpReaddirPacket:
method = "List"
case *sshFxpOpenPacket, *sshFxpOpendirPacket:
method = "Open"
case *sshFxpSetstatPacket:
case *sshFxpSetstatPacket, *sshFxpFsetstatPacket:
method = "Setstat"
case *sshFxpRenamePacket:
method = "Rename"
@ -331,7 +294,7 @@ func requestMethod(p hasPath) (method string) {
method = "Symlink"
case *sshFxpRemovePacket:
method = "Remove"
case *sshFxpStatPacket, *sshFxpLstatPacket:
case *sshFxpStatPacket, *sshFxpLstatPacket, *sshFxpFstatPacket:
method = "Stat"
case *sshFxpRmdirPacket:
method = "Rmdir"

View file

@ -1,8 +1,6 @@
package sftp
import (
"sync"
"github.com/stretchr/testify/assert"
"bytes"
@ -58,12 +56,10 @@ var filecontents = []byte("file-data.")
func testRequest(method string) *Request {
request := &Request{
Filepath: "./request_test.go",
Method: method,
Attrs: []byte("foo"),
Target: "foo",
state: &state{},
stateLock: &sync.RWMutex{},
Filepath: "./request_test.go",
Method: method,
Attrs: []byte("foo"),
Target: "foo",
}
return request
}