vendor: update all dependencies to latest versions
This commit is contained in:
parent
911d121bb9
commit
b017fcfe9a
3048 changed files with 537057 additions and 189681 deletions
32
vendor/github.com/pkg/sftp/client.go
generated
vendored
32
vendor/github.com/pkg/sftp/client.go
generated
vendored
|
@ -14,12 +14,32 @@ import (
|
|||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// MaxPacket sets the maximum size of the payload.
|
||||
func MaxPacket(size int) func(*Client) error {
|
||||
// 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.
|
||||
var InternalInconsistency = errors.New("internal inconsistency")
|
||||
|
||||
// A ClientOption is a function which applies configuration to a Client.
|
||||
type ClientOption func(*Client) error
|
||||
|
||||
// This is based on Openssh's max accepted size of 1<<18 - overhead
|
||||
const maxMaxPacket = (1 << 18) - 1024
|
||||
|
||||
// MaxPacket sets the maximum size of the payload. The size param must be
|
||||
// between 32768 (1<<15) and 261120 ((1 << 18) - 1024). The minimum size is
|
||||
// given by the RFC, while the maximum size is a de-facto standard based on
|
||||
// Openssh's SFTP server which won't accept packets much larger than that.
|
||||
//
|
||||
// Note if you aren't using Openssh's sftp server and get the error "failed to
|
||||
// send packet header: EOF" when copying a large file try lowering this number.
|
||||
func MaxPacket(size int) ClientOption {
|
||||
return func(c *Client) error {
|
||||
if size < 1<<15 {
|
||||
return errors.Errorf("size must be greater or equal to 32k")
|
||||
}
|
||||
if size > maxMaxPacket {
|
||||
return errors.Errorf("max packet size is too large (see docs)")
|
||||
}
|
||||
c.maxPacket = size
|
||||
return nil
|
||||
}
|
||||
|
@ -27,7 +47,7 @@ func MaxPacket(size int) func(*Client) error {
|
|||
|
||||
// NewClient creates a new SFTP client on conn, using zero or more option
|
||||
// functions.
|
||||
func NewClient(conn *ssh.Client, opts ...func(*Client) error) (*Client, error) {
|
||||
func NewClient(conn *ssh.Client, opts ...ClientOption) (*Client, error) {
|
||||
s, err := conn.NewSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -50,7 +70,7 @@ func NewClient(conn *ssh.Client, opts ...func(*Client) error) (*Client, error) {
|
|||
// NewClientPipe creates a new SFTP client given a Reader and a WriteCloser.
|
||||
// This can be used for connecting to an SFTP server over TCP/TLS or by using
|
||||
// the system's ssh client program (e.g. via exec.Command).
|
||||
func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...func(*Client) error) (*Client, error) {
|
||||
func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...ClientOption) (*Client, error) {
|
||||
sftp := &Client{
|
||||
clientConn: clientConn{
|
||||
conn: conn{
|
||||
|
@ -611,7 +631,7 @@ func (c *Client) Mkdir(path string) error {
|
|||
|
||||
// applyOptions applies options functions to the Client.
|
||||
// If an error is encountered, option processing ceases.
|
||||
func (c *Client) applyOptions(opts ...func(*Client) error) error {
|
||||
func (c *Client) applyOptions(opts ...ClientOption) error {
|
||||
for _, f := range opts {
|
||||
if err := f(c); err != nil {
|
||||
return err
|
||||
|
@ -791,7 +811,7 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
|
|||
|
||||
if inFlight == 0 {
|
||||
if firstErr.err == nil && len(pendingWrites) > 0 {
|
||||
return copied, errors.New("internal inconsistency")
|
||||
return copied, InternalInconsistency
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
16
vendor/github.com/pkg/sftp/example_test.go
generated
vendored
16
vendor/github.com/pkg/sftp/example_test.go
generated
vendored
|
@ -104,14 +104,26 @@ func ExampleClient_Mkdir_parents() {
|
|||
}
|
||||
defer client.Close()
|
||||
|
||||
ssh_fx_failure := uint32(4)
|
||||
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
|
||||
// this will also catch the case of the first character being a "/", i.e. an absolute path
|
||||
continue
|
||||
}
|
||||
parents = path.Join(parents, name)
|
||||
err = client.Mkdir(parents)
|
||||
if status, ok := err.(*sftp.StatusError); ok {
|
||||
if status.Code == ssh_fx_failure {
|
||||
if status.Code == sshFxFailure {
|
||||
var fi os.FileInfo
|
||||
fi, err = client.Stat(parents)
|
||||
if err == nil {
|
||||
|
|
40
vendor/github.com/pkg/sftp/packet-manager.go
generated
vendored
40
vendor/github.com/pkg/sftp/packet-manager.go
generated
vendored
|
@ -23,13 +23,13 @@ type packetManager struct {
|
|||
working *sync.WaitGroup
|
||||
}
|
||||
|
||||
func newPktMgr(sender packetSender) packetManager {
|
||||
s := packetManager{
|
||||
requests: make(chan requestPacket, sftpServerWorkerCount),
|
||||
responses: make(chan responsePacket, sftpServerWorkerCount),
|
||||
func newPktMgr(sender packetSender) *packetManager {
|
||||
s := &packetManager{
|
||||
requests: make(chan requestPacket, SftpServerWorkerCount),
|
||||
responses: make(chan responsePacket, SftpServerWorkerCount),
|
||||
fini: make(chan struct{}),
|
||||
incoming: make([]uint32, 0, sftpServerWorkerCount),
|
||||
outgoing: make([]responsePacket, 0, sftpServerWorkerCount),
|
||||
incoming: make([]uint32, 0, SftpServerWorkerCount),
|
||||
outgoing: make([]responsePacket, 0, SftpServerWorkerCount),
|
||||
sender: sender,
|
||||
working: &sync.WaitGroup{},
|
||||
}
|
||||
|
@ -39,19 +39,19 @@ func newPktMgr(sender packetSender) packetManager {
|
|||
|
||||
// register incoming packets to be handled
|
||||
// send id of 0 for packets without id
|
||||
func (s packetManager) incomingPacket(pkt requestPacket) {
|
||||
func (s *packetManager) incomingPacket(pkt requestPacket) {
|
||||
s.working.Add(1)
|
||||
s.requests <- pkt // buffer == sftpServerWorkerCount
|
||||
s.requests <- pkt // buffer == SftpServerWorkerCount
|
||||
}
|
||||
|
||||
// register outgoing packets as being ready
|
||||
func (s packetManager) readyPacket(pkt responsePacket) {
|
||||
func (s *packetManager) readyPacket(pkt responsePacket) {
|
||||
s.responses <- pkt
|
||||
s.working.Done()
|
||||
}
|
||||
|
||||
// shut down packetManager controller
|
||||
func (s packetManager) close() {
|
||||
func (s *packetManager) close() {
|
||||
// pause until current packets are processed
|
||||
s.working.Wait()
|
||||
close(s.fini)
|
||||
|
@ -63,15 +63,15 @@ func (s packetManager) close() {
|
|||
// transfers.
|
||||
func (s *packetManager) workerChan(runWorker func(requestChan)) requestChan {
|
||||
|
||||
rwChan := make(chan requestPacket, sftpServerWorkerCount)
|
||||
for i := 0; i < sftpServerWorkerCount; i++ {
|
||||
rwChan := make(chan requestPacket, SftpServerWorkerCount)
|
||||
for i := 0; i < SftpServerWorkerCount; i++ {
|
||||
runWorker(rwChan)
|
||||
}
|
||||
|
||||
cmdChan := make(chan requestPacket)
|
||||
runWorker(cmdChan)
|
||||
|
||||
pktChan := make(chan requestPacket, sftpServerWorkerCount)
|
||||
pktChan := make(chan requestPacket, SftpServerWorkerCount)
|
||||
go func() {
|
||||
// start with cmdChan
|
||||
curChan := cmdChan
|
||||
|
@ -147,10 +147,10 @@ func (s *packetManager) maybeSendPackets() {
|
|||
}
|
||||
}
|
||||
|
||||
func outfilter(o []responsePacket) []uint32 {
|
||||
res := make([]uint32, 0, len(o))
|
||||
for _, v := range o {
|
||||
res = append(res, v.id())
|
||||
}
|
||||
return res
|
||||
}
|
||||
//func outfilter(o []responsePacket) []uint32 {
|
||||
// res := make([]uint32, 0, len(o))
|
||||
// for _, v := range o {
|
||||
// res = append(res, v.id())
|
||||
// }
|
||||
// return res
|
||||
//}
|
||||
|
|
48
vendor/github.com/pkg/sftp/request-example.go
generated
vendored
48
vendor/github.com/pkg/sftp/request-example.go
generated
vendored
|
@ -11,7 +11,6 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
@ -26,7 +25,7 @@ func InMemHandler() Handlers {
|
|||
}
|
||||
|
||||
// Handlers
|
||||
func (fs *root) Fileread(r Request) (io.ReaderAt, error) {
|
||||
func (fs *root) Fileread(r *Request) (io.ReaderAt, error) {
|
||||
fs.filesLock.Lock()
|
||||
defer fs.filesLock.Unlock()
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
|
@ -42,7 +41,7 @@ func (fs *root) Fileread(r Request) (io.ReaderAt, error) {
|
|||
return file.ReaderAt()
|
||||
}
|
||||
|
||||
func (fs *root) Filewrite(r Request) (io.WriterAt, error) {
|
||||
func (fs *root) Filewrite(r *Request) (io.WriterAt, error) {
|
||||
fs.filesLock.Lock()
|
||||
defer fs.filesLock.Unlock()
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
|
@ -60,7 +59,7 @@ func (fs *root) Filewrite(r Request) (io.WriterAt, error) {
|
|||
return file.WriterAt()
|
||||
}
|
||||
|
||||
func (fs *root) Filecmd(r Request) error {
|
||||
func (fs *root) Filecmd(r *Request) error {
|
||||
fs.filesLock.Lock()
|
||||
defer fs.filesLock.Unlock()
|
||||
switch r.Method {
|
||||
|
@ -101,20 +100,27 @@ func (fs *root) Filecmd(r Request) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (fs *root) Fileinfo(r Request) ([]os.FileInfo, error) {
|
||||
type listerat []os.FileInfo
|
||||
|
||||
// Modeled after strings.Reader's ReadAt() implementation
|
||||
func (f listerat) ListAt(ls []os.FileInfo, offset int64) (int, error) {
|
||||
var n int
|
||||
if offset >= int64(len(f)) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(ls, f[offset:])
|
||||
if n < len(ls) {
|
||||
return n, io.EOF
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (fs *root) Filelist(r *Request) (ListerAt, error) {
|
||||
fs.filesLock.Lock()
|
||||
defer fs.filesLock.Unlock()
|
||||
|
||||
switch r.Method {
|
||||
case "List":
|
||||
var err error
|
||||
batch_size := 10
|
||||
current_offset := 0
|
||||
if token := r.LsNext(); token != "" {
|
||||
current_offset, err = strconv.Atoi(token)
|
||||
if err != nil {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
}
|
||||
ordered_names := []string{}
|
||||
for fn, _ := range fs.files {
|
||||
if filepath.Dir(fn) == r.Filepath {
|
||||
|
@ -126,21 +132,13 @@ func (fs *root) Fileinfo(r Request) ([]os.FileInfo, error) {
|
|||
for i, fn := range ordered_names {
|
||||
list[i] = fs.files[fn]
|
||||
}
|
||||
if len(list) < current_offset {
|
||||
return nil, io.EOF
|
||||
}
|
||||
new_offset := current_offset + batch_size
|
||||
if new_offset > len(list) {
|
||||
new_offset = len(list)
|
||||
}
|
||||
r.LsSave(strconv.Itoa(new_offset))
|
||||
return list[current_offset:new_offset], nil
|
||||
return listerat(list), nil
|
||||
case "Stat":
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []os.FileInfo{file}, nil
|
||||
return listerat([]os.FileInfo{file}), nil
|
||||
case "Readlink":
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
|
@ -152,7 +150,7 @@ func (fs *root) Fileinfo(r Request) ([]os.FileInfo, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
return []os.FileInfo{file}, nil
|
||||
return listerat([]os.FileInfo{file}), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
|
22
vendor/github.com/pkg/sftp/request-interfaces.go
generated
vendored
22
vendor/github.com/pkg/sftp/request-interfaces.go
generated
vendored
|
@ -10,21 +10,29 @@ import (
|
|||
|
||||
// FileReader should return an io.Reader for the filepath
|
||||
type FileReader interface {
|
||||
Fileread(Request) (io.ReaderAt, error)
|
||||
Fileread(*Request) (io.ReaderAt, error)
|
||||
}
|
||||
|
||||
// FileWriter should return an io.Writer for the filepath
|
||||
type FileWriter interface {
|
||||
Filewrite(Request) (io.WriterAt, error)
|
||||
Filewrite(*Request) (io.WriterAt, error)
|
||||
}
|
||||
|
||||
// FileCmder should return an error (rename, remove, setstate, etc.)
|
||||
type FileCmder interface {
|
||||
Filecmd(Request) error
|
||||
Filecmd(*Request) error
|
||||
}
|
||||
|
||||
// FileInfoer should return file listing info and errors (readdir, stat)
|
||||
// note stat requests would return a list of 1
|
||||
type FileInfoer interface {
|
||||
Fileinfo(Request) ([]os.FileInfo, error)
|
||||
// FileLister should return file info interface and errors (readdir, stat)
|
||||
type FileLister interface {
|
||||
Filelist(*Request) (ListerAt, error)
|
||||
}
|
||||
|
||||
// ListerAt does for file lists what io.ReaderAt does for files.
|
||||
// ListAt should return the number of entries copied and an io.EOF
|
||||
// error if at end of list. This is testable by comparing how many you
|
||||
// copied to how many could be copied (eg. n < len(ls) below).
|
||||
// The copy() builtin is best for the copying.
|
||||
type ListerAt interface {
|
||||
ListAt([]os.FileInfo, int64) (int, error)
|
||||
}
|
||||
|
|
67
vendor/github.com/pkg/sftp/request-server.go
generated
vendored
67
vendor/github.com/pkg/sftp/request-server.go
generated
vendored
|
@ -3,9 +3,9 @@ package sftp
|
|||
import (
|
||||
"encoding"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
|
@ -21,14 +21,14 @@ type Handlers struct {
|
|||
FileGet FileReader
|
||||
FilePut FileWriter
|
||||
FileCmd FileCmder
|
||||
FileInfo FileInfoer
|
||||
FileList FileLister
|
||||
}
|
||||
|
||||
// RequestServer abstracts the sftp protocol with an http request-like protocol
|
||||
type RequestServer struct {
|
||||
*serverConn
|
||||
Handlers Handlers
|
||||
pktMgr packetManager
|
||||
pktMgr *packetManager
|
||||
openRequests map[string]Request
|
||||
openRequestLock sync.RWMutex
|
||||
handleCount int
|
||||
|
@ -51,20 +51,22 @@ func NewRequestServer(rwc io.ReadWriteCloser, h Handlers) *RequestServer {
|
|||
}
|
||||
}
|
||||
|
||||
func (rs *RequestServer) nextRequest(r Request) string {
|
||||
// Note that we are explicitly saving the Request as a value.
|
||||
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
|
||||
}
|
||||
|
||||
func (rs *RequestServer) getRequest(handle string) (Request, bool) {
|
||||
// Returns pointer to new copy of Request object
|
||||
func (rs *RequestServer) getRequest(handle string) (*Request, bool) {
|
||||
rs.openRequestLock.RLock()
|
||||
defer rs.openRequestLock.RUnlock()
|
||||
r, ok := rs.openRequests[handle]
|
||||
return r, ok
|
||||
return &r, ok
|
||||
}
|
||||
|
||||
func (rs *RequestServer) closeRequest(handle string) {
|
||||
|
@ -130,7 +132,7 @@ func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
|
|||
rs.closeRequest(handle)
|
||||
rpkt = statusFromError(pkt, nil)
|
||||
case *sshFxpRealpathPacket:
|
||||
rpkt = cleanPath(pkt)
|
||||
rpkt = cleanPacketPath(pkt)
|
||||
case isOpener:
|
||||
handle := rs.nextRequest(requestFromPacket(pkt))
|
||||
rpkt = sshFxpHandlePacket{pkt.id(), handle}
|
||||
|
@ -142,7 +144,7 @@ func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
|
|||
} else {
|
||||
request = requestFromPacket(
|
||||
&sshFxpStatPacket{ID: pkt.id(), Path: request.Filepath})
|
||||
rpkt = rs.handle(request, pkt)
|
||||
rpkt = request.call(rs.Handlers, pkt)
|
||||
}
|
||||
case *sshFxpFsetstatPacket:
|
||||
handle := pkt.getHandle()
|
||||
|
@ -154,20 +156,23 @@ func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
|
|||
&sshFxpSetstatPacket{ID: pkt.id(), Path: request.Filepath,
|
||||
Flags: pkt.Flags, Attrs: pkt.Attrs,
|
||||
})
|
||||
rpkt = rs.handle(request, pkt)
|
||||
rpkt = request.call(rs.Handlers, pkt)
|
||||
}
|
||||
case hasHandle:
|
||||
handle := pkt.getHandle()
|
||||
request, ok := rs.getRequest(handle)
|
||||
request.update(pkt)
|
||||
if !ok {
|
||||
uerr := request.updateMethod(pkt)
|
||||
if !ok || uerr != nil {
|
||||
if uerr == nil {
|
||||
uerr = syscall.EBADF
|
||||
}
|
||||
rpkt = statusFromError(pkt, syscall.EBADF)
|
||||
} else {
|
||||
rpkt = rs.handle(request, pkt)
|
||||
rpkt = request.call(rs.Handlers, pkt)
|
||||
}
|
||||
case hasPath:
|
||||
request := requestFromPacket(pkt)
|
||||
rpkt = rs.handle(request, pkt)
|
||||
rpkt = request.call(rs.Handlers, pkt)
|
||||
default:
|
||||
return errors.Errorf("unexpected packet type %T", pkt)
|
||||
}
|
||||
|
@ -180,31 +185,24 @@ func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func cleanPath(pkt *sshFxpRealpathPacket) responsePacket {
|
||||
path := pkt.getPath()
|
||||
if !filepath.IsAbs(path) {
|
||||
path = "/" + path
|
||||
} // all paths are absolute
|
||||
|
||||
cleaned_path := filepath.Clean(path)
|
||||
func cleanPacketPath(pkt *sshFxpRealpathPacket) responsePacket {
|
||||
path := cleanPath(pkt.getPath())
|
||||
return &sshFxpNamePacket{
|
||||
ID: pkt.id(),
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: cleaned_path,
|
||||
LongName: cleaned_path,
|
||||
Name: path,
|
||||
LongName: path,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *RequestServer) handle(request Request, pkt requestPacket) responsePacket {
|
||||
// fmt.Println("Request Method: ", request.Method)
|
||||
rpkt, err := request.handle(rs.Handlers)
|
||||
if err != nil {
|
||||
err = errorAdapter(err)
|
||||
rpkt = statusFromError(pkt, err)
|
||||
func cleanPath(path string) string {
|
||||
cleanSlashPath := filepath.ToSlash(filepath.Clean(path))
|
||||
if !strings.HasPrefix(cleanSlashPath, "/") {
|
||||
return "/" + cleanSlashPath
|
||||
}
|
||||
return rpkt
|
||||
return cleanSlashPath
|
||||
}
|
||||
|
||||
// Wrap underlying connection methods to use packetManager
|
||||
|
@ -220,12 +218,3 @@ func (rs *RequestServer) sendPacket(m encoding.BinaryMarshaler) error {
|
|||
func (rs *RequestServer) sendError(p ider, err error) error {
|
||||
return rs.sendPacket(statusFromError(p, err))
|
||||
}
|
||||
|
||||
// os.ErrNotExist should convert to ssh_FX_NO_SUCH_FILE, but is not recognized
|
||||
// by statusFromError. So we convert to syscall.ENOENT which it does.
|
||||
func errorAdapter(err error) error {
|
||||
if err == os.ErrNotExist {
|
||||
return syscall.ENOENT
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
18
vendor/github.com/pkg/sftp/request-server_test.go
generated
vendored
18
vendor/github.com/pkg/sftp/request-server_test.go
generated
vendored
|
@ -315,6 +315,7 @@ func TestRequestReadlink(t *testing.T) {
|
|||
|
||||
func TestRequestReaddir(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
MaxFilelist = 22 // make not divisible by our test amount (100)
|
||||
defer p.Close()
|
||||
for i := 0; i < 100; i++ {
|
||||
fname := fmt.Sprintf("/foo_%02d", i)
|
||||
|
@ -327,3 +328,20 @@ func TestRequestReaddir(t *testing.T) {
|
|||
names := []string{di[18].Name(), di[81].Name()}
|
||||
assert.Equal(t, []string{"foo_18", "foo_81"}, names)
|
||||
}
|
||||
|
||||
func TestCleanPath(t *testing.T) {
|
||||
assert.Equal(t, "/", cleanPath("/"))
|
||||
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
|
||||
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))
|
||||
|
||||
}
|
||||
|
|
238
vendor/github.com/pkg/sftp/request.go
generated
vendored
238
vendor/github.com/pkg/sftp/request.go
generated
vendored
|
@ -11,6 +11,9 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// MaxFilelist is the max number of files to return in a readdir batch.
|
||||
var MaxFilelist int64 = 100
|
||||
|
||||
// Request contains the data and state for the incoming service request.
|
||||
type Request struct {
|
||||
// Get, Put, Setstat, Stat, Rename, Remove
|
||||
|
@ -20,72 +23,73 @@ type Request struct {
|
|||
Flags uint32
|
||||
Attrs []byte // convert to sub-struct
|
||||
Target string // for renames and sym-links
|
||||
// packet data
|
||||
pkt_id uint32
|
||||
packets chan packet_data
|
||||
// reader/writer/readdir from handlers
|
||||
stateLock *sync.RWMutex
|
||||
state *state
|
||||
}
|
||||
|
||||
type state struct {
|
||||
writerAt io.WriterAt
|
||||
readerAt io.ReaderAt
|
||||
endofdir bool // in case handler doesn't use EOF on file list
|
||||
readdirToken string
|
||||
writerAt io.WriterAt
|
||||
readerAt io.ReaderAt
|
||||
listerAt ListerAt
|
||||
lsoffset int64
|
||||
}
|
||||
|
||||
type packet_data struct {
|
||||
id uint32
|
||||
_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 {
|
||||
func requestFromPacket(pkt hasPath) *Request {
|
||||
method := requestMethod(pkt)
|
||||
request := NewRequest(method, pkt.getPath())
|
||||
request.pkt_id = pkt.id()
|
||||
switch p := pkt.(type) {
|
||||
case *sshFxpSetstatPacket:
|
||||
request.Flags = p.Flags
|
||||
request.Attrs = p.Attrs.([]byte)
|
||||
case *sshFxpRenamePacket:
|
||||
request.Target = filepath.Clean(p.Newpath)
|
||||
request.Target = cleanPath(p.Newpath)
|
||||
case *sshFxpSymlinkPacket:
|
||||
request.Target = filepath.Clean(p.Linkpath)
|
||||
request.Target = cleanPath(p.Linkpath)
|
||||
}
|
||||
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 := Request{Method: method, Filepath: filepath.Clean(path)}
|
||||
request.packets = make(chan packet_data, sftpServerWorkerCount)
|
||||
request.state = &state{}
|
||||
request.stateLock = &sync.RWMutex{}
|
||||
func NewRequest(method, path string) *Request {
|
||||
request := newRequest()
|
||||
request.Method = method
|
||||
request.Filepath = cleanPath(path)
|
||||
return request
|
||||
}
|
||||
|
||||
// LsSave takes a token to keep track of file list batches. Openssh uses a
|
||||
// batch size of 100, so I suggest sticking close to that.
|
||||
func (r Request) LsSave(token string) {
|
||||
// Returns current offset for file list
|
||||
func (r *Request) lsNext() int64 {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
r.state.readdirToken = token
|
||||
return r.state.lsoffset
|
||||
}
|
||||
|
||||
// LsNext should return the token from the previous call to know which batch
|
||||
// to return next.
|
||||
func (r Request) LsNext() string {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
return r.state.readdirToken
|
||||
// Increases next offset
|
||||
func (r *Request) lsInc(offset int64) {
|
||||
r.stateLock.Lock()
|
||||
defer r.stateLock.Unlock()
|
||||
r.state.lsoffset = r.state.lsoffset + offset
|
||||
}
|
||||
|
||||
// manage file read/write state
|
||||
func (r Request) setFileState(s interface{}) {
|
||||
func (r *Request) setFileState(s interface{}) {
|
||||
r.stateLock.Lock()
|
||||
defer r.stateLock.Unlock()
|
||||
switch s := s.(type) {
|
||||
|
@ -93,38 +97,33 @@ func (r Request) setFileState(s interface{}) {
|
|||
r.state.writerAt = s
|
||||
case io.ReaderAt:
|
||||
r.state.readerAt = s
|
||||
|
||||
case ListerAt:
|
||||
r.state.listerAt = s
|
||||
case int64:
|
||||
r.state.lsoffset = s
|
||||
}
|
||||
}
|
||||
|
||||
func (r Request) getWriter() io.WriterAt {
|
||||
func (r *Request) getWriter() io.WriterAt {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
return r.state.writerAt
|
||||
}
|
||||
|
||||
func (r Request) getReader() io.ReaderAt {
|
||||
func (r *Request) getReader() io.ReaderAt {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
return r.state.readerAt
|
||||
}
|
||||
|
||||
// For backwards compatibility. The Handler didn't have batch handling at
|
||||
// first, and just always assumed 1 batch. This preserves that behavior.
|
||||
func (r Request) setEOD(eod bool) {
|
||||
func (r *Request) getLister() ListerAt {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
r.state.endofdir = eod
|
||||
}
|
||||
|
||||
func (r Request) getEOD() bool {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
return r.state.endofdir
|
||||
return r.state.listerAt
|
||||
}
|
||||
|
||||
// Close reader/writer if possible
|
||||
func (r Request) close() {
|
||||
func (r *Request) close() {
|
||||
rd := r.getReader()
|
||||
if c, ok := rd.(io.Closer); ok {
|
||||
c.Close()
|
||||
|
@ -135,112 +134,130 @@ func (r Request) close() {
|
|||
}
|
||||
}
|
||||
|
||||
// push packet_data into fifo
|
||||
func (r Request) pushPacket(pd packet_data) {
|
||||
r.packets <- pd
|
||||
}
|
||||
|
||||
// pop packet_data into fifo
|
||||
func (r *Request) popPacket() packet_data {
|
||||
return <-r.packets
|
||||
}
|
||||
|
||||
// called from worker to handle packet/request
|
||||
func (r Request) handle(handlers Handlers) (responsePacket, error) {
|
||||
var err error
|
||||
var rpkt responsePacket
|
||||
func (r *Request) call(handlers Handlers, pkt requestPacket) responsePacket {
|
||||
pd := packetData(pkt)
|
||||
switch r.Method {
|
||||
case "Get":
|
||||
rpkt, err = fileget(handlers.FileGet, r)
|
||||
return fileget(handlers.FileGet, r, pd)
|
||||
case "Put": // add "Append" to this to handle append only file writes
|
||||
rpkt, err = fileput(handlers.FilePut, r)
|
||||
return fileput(handlers.FilePut, r, pd)
|
||||
case "Setstat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove":
|
||||
rpkt, err = filecmd(handlers.FileCmd, r)
|
||||
return filecmd(handlers.FileCmd, r, pd)
|
||||
case "List", "Stat", "Readlink":
|
||||
rpkt, err = fileinfo(handlers.FileInfo, r)
|
||||
return filelist(handlers.FileList, r, pd)
|
||||
default:
|
||||
return rpkt, errors.Errorf("unexpected method: %s", r.Method)
|
||||
return statusFromError(pkt,
|
||||
errors.Errorf("unexpected method: %s", r.Method))
|
||||
}
|
||||
return rpkt, err
|
||||
}
|
||||
|
||||
// file data for additional read/write packets
|
||||
func packetData(p requestPacket) packet_data {
|
||||
pd := packet_data{_id: p.id()}
|
||||
switch p := p.(type) {
|
||||
case *sshFxpReadPacket:
|
||||
pd.length = p.Len
|
||||
pd.offset = int64(p.Offset)
|
||||
case *sshFxpWritePacket:
|
||||
pd.data = p.Data
|
||||
pd.length = p.Length
|
||||
pd.offset = int64(p.Offset)
|
||||
}
|
||||
return pd
|
||||
}
|
||||
|
||||
// wrap FileReader handler
|
||||
func fileget(h FileReader, r Request) (responsePacket, error) {
|
||||
func fileget(h FileReader, r *Request, pd packet_data) responsePacket {
|
||||
var err error
|
||||
reader := r.getReader()
|
||||
if reader == nil {
|
||||
reader, err = h.Fileread(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return statusFromError(pd, err)
|
||||
}
|
||||
r.setFileState(reader)
|
||||
}
|
||||
|
||||
pd := r.popPacket()
|
||||
data := make([]byte, clamp(pd.length, maxTxPacket))
|
||||
n, err := reader.ReadAt(data, pd.offset)
|
||||
// only return EOF erro if no data left to read
|
||||
if err != nil && (err != io.EOF || n == 0) {
|
||||
return nil, err
|
||||
return statusFromError(pd, err)
|
||||
}
|
||||
return &sshFxpDataPacket{
|
||||
ID: pd.id,
|
||||
ID: pd.id(),
|
||||
Length: uint32(n),
|
||||
Data: data[:n],
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// wrap FileWriter handler
|
||||
func fileput(h FileWriter, r Request) (responsePacket, error) {
|
||||
func fileput(h FileWriter, r *Request, pd packet_data) responsePacket {
|
||||
var err error
|
||||
writer := r.getWriter()
|
||||
if writer == nil {
|
||||
writer, err = h.Filewrite(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return statusFromError(pd, err)
|
||||
}
|
||||
r.setFileState(writer)
|
||||
}
|
||||
|
||||
pd := r.popPacket()
|
||||
_, err = writer.WriteAt(pd.data, pd.offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return statusFromError(pd, err)
|
||||
}
|
||||
return &sshFxpStatusPacket{
|
||||
ID: pd.id,
|
||||
ID: pd.id(),
|
||||
StatusError: StatusError{
|
||||
Code: ssh_FX_OK,
|
||||
}}, nil
|
||||
}}
|
||||
}
|
||||
|
||||
// wrap FileCmder handler
|
||||
func filecmd(h FileCmder, r Request) (responsePacket, error) {
|
||||
func filecmd(h FileCmder, r *Request, pd packet_data) responsePacket {
|
||||
err := h.Filecmd(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return statusFromError(pd, err)
|
||||
}
|
||||
return &sshFxpStatusPacket{
|
||||
ID: r.pkt_id,
|
||||
ID: pd.id(),
|
||||
StatusError: StatusError{
|
||||
Code: ssh_FX_OK,
|
||||
}}, nil
|
||||
}}
|
||||
}
|
||||
|
||||
// wrap FileInfoer handler
|
||||
func fileinfo(h FileInfoer, r Request) (responsePacket, error) {
|
||||
if r.getEOD() {
|
||||
return nil, io.EOF
|
||||
}
|
||||
finfo, err := h.Fileinfo(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// wrap FileLister handler
|
||||
func filelist(h FileLister, r *Request, pd packet_data) responsePacket {
|
||||
var err error
|
||||
lister := r.getLister()
|
||||
if lister == nil {
|
||||
lister, err = h.Filelist(r)
|
||||
if err != nil {
|
||||
return statusFromError(pd, err)
|
||||
}
|
||||
r.setFileState(lister)
|
||||
}
|
||||
|
||||
offset := r.lsNext()
|
||||
finfo := make([]os.FileInfo, MaxFilelist)
|
||||
n, err := lister.ListAt(finfo, offset)
|
||||
r.lsInc(int64(n))
|
||||
// ignore EOF as we only return it when there are no results
|
||||
finfo = finfo[:n] // avoid need for nil tests below
|
||||
|
||||
switch r.Method {
|
||||
case "List":
|
||||
pd := r.popPacket()
|
||||
dirname := path.Base(r.Filepath)
|
||||
ret := &sshFxpNamePacket{ID: pd.id}
|
||||
if err != nil && err != io.EOF {
|
||||
return statusFromError(pd, err)
|
||||
}
|
||||
if n == 0 {
|
||||
return statusFromError(pd, io.EOF)
|
||||
}
|
||||
dirname := filepath.ToSlash(path.Base(r.Filepath))
|
||||
ret := &sshFxpNamePacket{ID: pd.id()}
|
||||
|
||||
for _, fi := range finfo {
|
||||
ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
|
||||
Name: fi.Name(),
|
||||
|
@ -248,63 +265,56 @@ func fileinfo(h FileInfoer, r Request) (responsePacket, error) {
|
|||
Attrs: []interface{}{fi},
|
||||
})
|
||||
}
|
||||
// No entries means we should return EOF as the Handler didn't.
|
||||
if len(finfo) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
// If files are returned but no token is set, return EOF next call.
|
||||
if r.LsNext() == "" {
|
||||
r.setEOD(true)
|
||||
}
|
||||
return ret, nil
|
||||
return ret
|
||||
case "Stat":
|
||||
if len(finfo) == 0 {
|
||||
if err != nil && err != io.EOF {
|
||||
return statusFromError(pd, err)
|
||||
}
|
||||
if n == 0 {
|
||||
err = &os.PathError{Op: "stat", Path: r.Filepath,
|
||||
Err: syscall.ENOENT}
|
||||
return nil, err
|
||||
return statusFromError(pd, err)
|
||||
}
|
||||
return &sshFxpStatResponse{
|
||||
ID: r.pkt_id,
|
||||
ID: pd.id(),
|
||||
info: finfo[0],
|
||||
}, nil
|
||||
}
|
||||
case "Readlink":
|
||||
if len(finfo) == 0 {
|
||||
if err != nil && err != io.EOF {
|
||||
return statusFromError(pd, err)
|
||||
}
|
||||
if n == 0 {
|
||||
err = &os.PathError{Op: "readlink", Path: r.Filepath,
|
||||
Err: syscall.ENOENT}
|
||||
return nil, err
|
||||
return statusFromError(pd, err)
|
||||
}
|
||||
filename := finfo[0].Name()
|
||||
return &sshFxpNamePacket{
|
||||
ID: r.pkt_id,
|
||||
ID: pd.id(),
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: filename,
|
||||
LongName: filename,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
default:
|
||||
err = errors.Errorf("unexpected method: %s", r.Method)
|
||||
return statusFromError(pd, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// file data for additional read/write packets
|
||||
func (r *Request) update(p hasHandle) error {
|
||||
pd := packet_data{id: p.id()}
|
||||
func (r *Request) updateMethod(p hasHandle) error {
|
||||
switch p := p.(type) {
|
||||
case *sshFxpReadPacket:
|
||||
r.Method = "Get"
|
||||
pd.length = p.Len
|
||||
pd.offset = int64(p.Offset)
|
||||
case *sshFxpWritePacket:
|
||||
r.Method = "Put"
|
||||
pd.data = p.Data
|
||||
pd.length = p.Length
|
||||
pd.offset = int64(p.Offset)
|
||||
case *sshFxpReaddirPacket:
|
||||
r.Method = "List"
|
||||
default:
|
||||
return errors.Errorf("unexpected packet type %T", p)
|
||||
}
|
||||
r.pushPacket(pd)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
85
vendor/github.com/pkg/sftp/request_test.go
generated
vendored
85
vendor/github.com/pkg/sftp/request_test.go
generated
vendored
|
@ -18,28 +18,25 @@ type testHandler struct {
|
|||
err error // dummy error, should be file related
|
||||
}
|
||||
|
||||
func (t *testHandler) Fileread(r Request) (io.ReaderAt, error) {
|
||||
func (t *testHandler) Fileread(r *Request) (io.ReaderAt, error) {
|
||||
if t.err != nil {
|
||||
return nil, t.err
|
||||
}
|
||||
return bytes.NewReader(t.filecontents), nil
|
||||
}
|
||||
|
||||
func (t *testHandler) Filewrite(r Request) (io.WriterAt, error) {
|
||||
func (t *testHandler) Filewrite(r *Request) (io.WriterAt, error) {
|
||||
if t.err != nil {
|
||||
return nil, t.err
|
||||
}
|
||||
return io.WriterAt(t.output), nil
|
||||
}
|
||||
|
||||
func (t *testHandler) Filecmd(r Request) error {
|
||||
if t.err != nil {
|
||||
return t.err
|
||||
}
|
||||
return nil
|
||||
func (t *testHandler) Filecmd(r *Request) error {
|
||||
return t.err
|
||||
}
|
||||
|
||||
func (t *testHandler) Fileinfo(r Request) ([]os.FileInfo, error) {
|
||||
func (t *testHandler) Filelist(r *Request) (ListerAt, error) {
|
||||
if t.err != nil {
|
||||
return nil, t.err
|
||||
}
|
||||
|
@ -51,7 +48,7 @@ func (t *testHandler) Fileinfo(r Request) ([]os.FileInfo, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []os.FileInfo{fi}, nil
|
||||
return listerat([]os.FileInfo{fi}), nil
|
||||
}
|
||||
|
||||
// make sure len(fakefile) == len(filecontents)
|
||||
|
@ -59,21 +56,15 @@ type fakefile [10]byte
|
|||
|
||||
var filecontents = []byte("file-data.")
|
||||
|
||||
func testRequest(method string) Request {
|
||||
request := Request{
|
||||
func testRequest(method string) *Request {
|
||||
request := &Request{
|
||||
Filepath: "./request_test.go",
|
||||
Method: method,
|
||||
Attrs: []byte("foo"),
|
||||
Target: "foo",
|
||||
packets: make(chan packet_data, sftpServerWorkerCount),
|
||||
state: &state{},
|
||||
stateLock: &sync.RWMutex{},
|
||||
}
|
||||
for _, p := range []packet_data{
|
||||
packet_data{id: 1, data: filecontents[:5], length: 5},
|
||||
packet_data{id: 2, data: filecontents[5:], length: 5, offset: 5}} {
|
||||
request.packets <- p
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
|
@ -98,7 +89,7 @@ func newTestHandlers() Handlers {
|
|||
FileGet: handler,
|
||||
FilePut: handler,
|
||||
FileCmd: handler,
|
||||
FileInfo: handler,
|
||||
FileList: handler,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,15 +111,30 @@ func statusOk(t *testing.T, p interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
// fake/test packet
|
||||
type fakePacket struct {
|
||||
myid uint32
|
||||
handle string
|
||||
}
|
||||
|
||||
func (f fakePacket) id() uint32 {
|
||||
return f.myid
|
||||
}
|
||||
|
||||
func (f fakePacket) getHandle() string {
|
||||
return f.handle
|
||||
}
|
||||
func (fakePacket) UnmarshalBinary(d []byte) error { return nil }
|
||||
|
||||
func TestRequestGet(t *testing.T) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest("Get")
|
||||
// req.length is 5, so we test reads in 5 byte chunks
|
||||
for i, txt := range []string{"file-", "data."} {
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
dpkt := pkt.(*sshFxpDataPacket)
|
||||
assert.Equal(t, dpkt.id(), uint32(i+1))
|
||||
pkt := &sshFxpReadPacket{uint32(i), "a", uint64(i * 5), 5}
|
||||
rpkt := request.call(handlers, pkt)
|
||||
dpkt := rpkt.(*sshFxpDataPacket)
|
||||
assert.Equal(t, dpkt.id(), uint32(i))
|
||||
assert.Equal(t, string(dpkt.Data), txt)
|
||||
}
|
||||
}
|
||||
|
@ -136,26 +142,25 @@ func TestRequestGet(t *testing.T) {
|
|||
func TestRequestPut(t *testing.T) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest("Put")
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
statusOk(t, pkt)
|
||||
pkt, err = request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
statusOk(t, pkt)
|
||||
pkt := &sshFxpWritePacket{0, "a", 0, 5, []byte("file-")}
|
||||
rpkt := request.call(handlers, pkt)
|
||||
statusOk(t, rpkt)
|
||||
pkt = &sshFxpWritePacket{1, "a", 5, 5, []byte("data.")}
|
||||
rpkt = request.call(handlers, pkt)
|
||||
statusOk(t, rpkt)
|
||||
assert.Equal(t, "file-data.", handlers.getOutString())
|
||||
}
|
||||
|
||||
func TestRequestCmdr(t *testing.T) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest("Mkdir")
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
statusOk(t, pkt)
|
||||
pkt := fakePacket{myid: 1}
|
||||
rpkt := request.call(handlers, pkt)
|
||||
statusOk(t, rpkt)
|
||||
|
||||
handlers.returnError()
|
||||
pkt, err = request.handle(handlers)
|
||||
assert.Nil(t, pkt)
|
||||
assert.Equal(t, err, errTest)
|
||||
rpkt = request.call(handlers, pkt)
|
||||
assert.Equal(t, rpkt, statusFromError(rpkt, errTest))
|
||||
}
|
||||
|
||||
func TestRequestInfoList(t *testing.T) { testInfoMethod(t, "List") }
|
||||
|
@ -163,9 +168,9 @@ func TestRequestInfoReadlink(t *testing.T) { testInfoMethod(t, "Readlink") }
|
|||
func TestRequestInfoStat(t *testing.T) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest("Stat")
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
spkt, ok := pkt.(*sshFxpStatResponse)
|
||||
pkt := fakePacket{myid: 1}
|
||||
rpkt := request.call(handlers, pkt)
|
||||
spkt, ok := rpkt.(*sshFxpStatResponse)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, spkt.info.Name(), "request_test.go")
|
||||
}
|
||||
|
@ -173,9 +178,9 @@ func TestRequestInfoStat(t *testing.T) {
|
|||
func testInfoMethod(t *testing.T, method string) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest(method)
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
npkt, ok := pkt.(*sshFxpNamePacket)
|
||||
pkt := fakePacket{myid: 1}
|
||||
rpkt := request.call(handlers, pkt)
|
||||
npkt, ok := rpkt.(*sshFxpNamePacket)
|
||||
assert.True(t, ok)
|
||||
assert.IsType(t, sshFxpNameAttr{}, npkt.NameAttrs[0])
|
||||
assert.Equal(t, npkt.NameAttrs[0].Name, "request_test.go")
|
||||
|
|
98
vendor/github.com/pkg/sftp/server.go
generated
vendored
98
vendor/github.com/pkg/sftp/server.go
generated
vendored
|
@ -18,7 +18,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
sftpServerWorkerCount = 8
|
||||
SftpServerWorkerCount = 8
|
||||
)
|
||||
|
||||
// Server is an SSH File Transfer Protocol (sftp) server.
|
||||
|
@ -29,7 +29,7 @@ type Server struct {
|
|||
*serverConn
|
||||
debugStream io.Writer
|
||||
readOnly bool
|
||||
pktMgr packetManager
|
||||
pktMgr *packetManager
|
||||
openFiles map[string]*os.File
|
||||
openFilesLock sync.RWMutex
|
||||
handleCount int
|
||||
|
@ -230,8 +230,7 @@ func handlePacket(s *Server, p interface{}) error {
|
|||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
f = filepath.Clean(f)
|
||||
f = filepath.ToSlash(f) // make path more Unix like on windows servers
|
||||
f = cleanPath(f)
|
||||
return s.sendPacket(sshFxpNamePacket{
|
||||
ID: p.ID,
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
|
@ -555,6 +554,8 @@ func statusFromError(p ider, err error) sshFxpStatusPacket {
|
|||
ret.StatusError.msg = err.Error()
|
||||
if err == io.EOF {
|
||||
ret.StatusError.Code = ssh_FX_EOF
|
||||
} else if err == os.ErrNotExist {
|
||||
ret.StatusError.Code = ssh_FX_NO_SUCH_FILE
|
||||
} else if errno, ok := err.(syscall.Errno); ok {
|
||||
ret.StatusError.Code = translateErrno(errno)
|
||||
} else if pathError, ok := err.(*os.PathError); ok {
|
||||
|
@ -573,3 +574,92 @@ func clamp(v, max uint32) uint32 {
|
|||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func runLsTypeWord(dirent os.FileInfo) string {
|
||||
// find first character, the type char
|
||||
// b Block special file.
|
||||
// c Character special file.
|
||||
// d Directory.
|
||||
// l Symbolic link.
|
||||
// s Socket link.
|
||||
// p FIFO.
|
||||
// - Regular file.
|
||||
tc := '-'
|
||||
mode := dirent.Mode()
|
||||
if (mode & os.ModeDir) != 0 {
|
||||
tc = 'd'
|
||||
} else if (mode & os.ModeDevice) != 0 {
|
||||
tc = 'b'
|
||||
if (mode & os.ModeCharDevice) != 0 {
|
||||
tc = 'c'
|
||||
}
|
||||
} else if (mode & os.ModeSymlink) != 0 {
|
||||
tc = 'l'
|
||||
} else if (mode & os.ModeSocket) != 0 {
|
||||
tc = 's'
|
||||
} else if (mode & os.ModeNamedPipe) != 0 {
|
||||
tc = 'p'
|
||||
}
|
||||
|
||||
// owner
|
||||
orc := '-'
|
||||
if (mode & 0400) != 0 {
|
||||
orc = 'r'
|
||||
}
|
||||
owc := '-'
|
||||
if (mode & 0200) != 0 {
|
||||
owc = 'w'
|
||||
}
|
||||
oxc := '-'
|
||||
ox := (mode & 0100) != 0
|
||||
setuid := (mode & os.ModeSetuid) != 0
|
||||
if ox && setuid {
|
||||
oxc = 's'
|
||||
} else if setuid {
|
||||
oxc = 'S'
|
||||
} else if ox {
|
||||
oxc = 'x'
|
||||
}
|
||||
|
||||
// group
|
||||
grc := '-'
|
||||
if (mode & 040) != 0 {
|
||||
grc = 'r'
|
||||
}
|
||||
gwc := '-'
|
||||
if (mode & 020) != 0 {
|
||||
gwc = 'w'
|
||||
}
|
||||
gxc := '-'
|
||||
gx := (mode & 010) != 0
|
||||
setgid := (mode & os.ModeSetgid) != 0
|
||||
if gx && setgid {
|
||||
gxc = 's'
|
||||
} else if setgid {
|
||||
gxc = 'S'
|
||||
} else if gx {
|
||||
gxc = 'x'
|
||||
}
|
||||
|
||||
// all / others
|
||||
arc := '-'
|
||||
if (mode & 04) != 0 {
|
||||
arc = 'r'
|
||||
}
|
||||
awc := '-'
|
||||
if (mode & 02) != 0 {
|
||||
awc = 'w'
|
||||
}
|
||||
axc := '-'
|
||||
ax := (mode & 01) != 0
|
||||
sticky := (mode & os.ModeSticky) != 0
|
||||
if ax && sticky {
|
||||
axc = 't'
|
||||
} else if sticky {
|
||||
axc = 'T'
|
||||
} else if ax {
|
||||
axc = 'x'
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%c%c%c%c%c%c%c%c%c%c", tc, orc, owc, oxc, grc, gwc, gxc, arc, awc, axc)
|
||||
}
|
||||
|
|
51
vendor/github.com/pkg/sftp/server_integration_test.go
generated
vendored
51
vendor/github.com/pkg/sftp/server_integration_test.go
generated
vendored
|
@ -6,7 +6,12 @@ package sftp
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
crand "crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
@ -316,7 +321,7 @@ func (chsvr *sshSessionChannelServer) handleSubsystem(req *ssh.Request) error {
|
|||
// starts an ssh server to test. returns: host string and port
|
||||
func testServer(t *testing.T, useSubsystem bool, readonly bool) (net.Listener, string, int) {
|
||||
if !*testIntegration {
|
||||
t.Skip("skipping intergration test")
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
|
@ -357,17 +362,59 @@ func testServer(t *testing.T, useSubsystem bool, readonly bool) (net.Listener, s
|
|||
return listener, host, port
|
||||
}
|
||||
|
||||
func makeDummyKey() (string, error) {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), crand.Reader)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot generate key: %v", err)
|
||||
}
|
||||
der, err := x509.MarshalECPrivateKey(priv)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot marshal key: %v", err)
|
||||
}
|
||||
block := &pem.Block{Type: "EC PRIVATE KEY", Bytes: der}
|
||||
f, err := ioutil.TempFile("", "sftp-test-key-")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot create temp file: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if f != nil {
|
||||
_ = f.Close()
|
||||
_ = os.Remove(f.Name())
|
||||
}
|
||||
}()
|
||||
if err := pem.Encode(f, block); err != nil {
|
||||
return "", fmt.Errorf("cannot write key: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return "", fmt.Errorf("error closing key file: %v", err)
|
||||
}
|
||||
path := f.Name()
|
||||
f = nil
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func runSftpClient(t *testing.T, script string, path string, host string, port int) (string, error) {
|
||||
// if sftp client binary is unavailable, skip test
|
||||
if _, err := os.Stat(*testSftpClientBin); err != nil {
|
||||
t.Skip("sftp client binary unavailable")
|
||||
}
|
||||
|
||||
// make a dummy key so we don't rely on ssh-agent
|
||||
dummyKey, err := makeDummyKey()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.Remove(dummyKey)
|
||||
|
||||
args := []string{
|
||||
// "-vvvv",
|
||||
"-b", "-",
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
"-o", "LogLevel=ERROR",
|
||||
"-o", "UserKnownHostsFile /dev/null",
|
||||
// do not trigger ssh-agent prompting
|
||||
"-o", "IdentityFile=" + dummyKey,
|
||||
"-o", "IdentitiesOnly=yes",
|
||||
"-P", fmt.Sprintf("%d", port), fmt.Sprintf("%s:%s", host, path),
|
||||
}
|
||||
cmd := exec.Command(*testSftpClientBin, args...)
|
||||
|
@ -378,7 +425,7 @@ func runSftpClient(t *testing.T, script string, path string, host string, port i
|
|||
if err := cmd.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
err := cmd.Wait()
|
||||
err = cmd.Wait()
|
||||
return string(stdout.Bytes()), err
|
||||
}
|
||||
|
||||
|
|
24
vendor/github.com/pkg/sftp/server_stubs.go
generated
vendored
24
vendor/github.com/pkg/sftp/server_stubs.go
generated
vendored
|
@ -4,9 +4,29 @@ package sftp
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func runLs(dirname string, dirent os.FileInfo) string {
|
||||
return path.Join(dirname, dirent.Name())
|
||||
typeword := runLsTypeWord(dirent)
|
||||
numLinks := 1
|
||||
if dirent.IsDir() {
|
||||
numLinks = 0
|
||||
}
|
||||
username := "root"
|
||||
groupname := "root"
|
||||
mtime := dirent.ModTime()
|
||||
monthStr := mtime.Month().String()[0:3]
|
||||
day := mtime.Day()
|
||||
year := mtime.Year()
|
||||
now := time.Now()
|
||||
isOld := mtime.Before(now.Add(-time.Hour * 24 * 365 / 2))
|
||||
|
||||
yearOrTime := fmt.Sprintf("%02d:%02d", mtime.Hour(), mtime.Minute())
|
||||
if isOld {
|
||||
yearOrTime = fmt.Sprintf("%d", year)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %4d %-8s %-8s %8d %s %2d %5s %s", typeword, numLinks, username, groupname, dirent.Size(), monthStr, day, yearOrTime, dirent.Name())
|
||||
}
|
||||
|
|
150
vendor/github.com/pkg/sftp/server_test.go
generated
vendored
150
vendor/github.com/pkg/sftp/server_test.go
generated
vendored
|
@ -1,11 +1,159 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
typeDirectory = "d"
|
||||
typeFile = "[^d]"
|
||||
)
|
||||
|
||||
func TestRunLsWithExamplesDirectory(t *testing.T) {
|
||||
path := "examples"
|
||||
item, _ := os.Stat(path)
|
||||
result := runLs(path, item)
|
||||
runLsTestHelper(t, result, typeDirectory, path)
|
||||
}
|
||||
|
||||
func TestRunLsWithLicensesFile(t *testing.T) {
|
||||
path := "LICENSE"
|
||||
item, _ := os.Stat(path)
|
||||
result := runLs(path, item)
|
||||
runLsTestHelper(t, result, typeFile, path)
|
||||
}
|
||||
|
||||
/*
|
||||
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 runLsTestHelper(t *testing.T, result, expectedType, path string) {
|
||||
// using regular expressions to make tests work on all systems
|
||||
// a virtual file system (like afero) would be needed to mock valid filesystem checks
|
||||
// expected layout is:
|
||||
// drwxr-xr-x 8 501 20 272 Aug 9 19:46 examples
|
||||
|
||||
// permissions (len 10, "drwxr-xr-x")
|
||||
got := result[0:10]
|
||||
if ok, err := regexp.MatchString("^"+expectedType+"[rwx-]{9}$", got); !ok {
|
||||
t.Errorf("runLs(%#v, *FileInfo): permission field mismatch, expected dir, got: %#v, err: %#v", path, got, err)
|
||||
}
|
||||
|
||||
// space
|
||||
got = result[10:11]
|
||||
if ok, err := regexp.MatchString("^\\s$", got); !ok {
|
||||
t.Errorf("runLs(%#v, *FileInfo): spacer 1 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
|
||||
}
|
||||
|
||||
// link count (len 3, number)
|
||||
got = result[12:15]
|
||||
if ok, err := regexp.MatchString("^\\s*[0-9]+$", got); !ok {
|
||||
t.Errorf("runLs(%#v, *FileInfo): link count field mismatch, got: %#v, err: %#v", path, got, err)
|
||||
}
|
||||
|
||||
// spacer
|
||||
got = result[15:16]
|
||||
if ok, err := regexp.MatchString("^\\s$", got); !ok {
|
||||
t.Errorf("runLs(%#v, *FileInfo): spacer 2 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
|
||||
}
|
||||
|
||||
// username / uid (len 8, number or string)
|
||||
got = result[16:24]
|
||||
if ok, err := regexp.MatchString("^[^\\s]{1,8}\\s*$", got); !ok {
|
||||
t.Errorf("runLs(%#v, *FileInfo): username / uid mismatch, expected user, got: %#v, err: %#v", path, got, err)
|
||||
}
|
||||
|
||||
// spacer
|
||||
got = result[24:25]
|
||||
if ok, err := regexp.MatchString("^\\s$", got); !ok {
|
||||
t.Errorf("runLs(%#v, *FileInfo): spacer 3 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
|
||||
}
|
||||
|
||||
// groupname / gid (len 8, number or string)
|
||||
got = result[25:33]
|
||||
if ok, err := regexp.MatchString("^[^\\s]{1,8}\\s*$", got); !ok {
|
||||
t.Errorf("runLs(%#v, *FileInfo): groupname / gid mismatch, expected group, got: %#v, err: %#v", path, got, err)
|
||||
}
|
||||
|
||||
// spacer
|
||||
got = result[33:34]
|
||||
if ok, err := regexp.MatchString("^\\s$", got); !ok {
|
||||
t.Errorf("runLs(%#v, *FileInfo): spacer 4 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
|
||||
}
|
||||
|
||||
// filesize (len 8)
|
||||
got = result[34:42]
|
||||
if ok, err := regexp.MatchString("^\\s*[0-9]+$", got); !ok {
|
||||
t.Errorf("runLs(%#v, *FileInfo): filesize field mismatch, expected size in bytes, got: %#v, err: %#v", path, got, err)
|
||||
}
|
||||
|
||||
// spacer
|
||||
got = result[42:43]
|
||||
if ok, err := regexp.MatchString("^\\s$", got); !ok {
|
||||
t.Errorf("runLs(%#v, *FileInfo): spacer 5 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
|
||||
}
|
||||
|
||||
// mod time (len 12, e.g. Aug 9 19:46)
|
||||
got = result[43:55]
|
||||
layout := "Jan 2 15:04"
|
||||
_, err := time.Parse(layout, got)
|
||||
|
||||
if err != nil {
|
||||
layout = "Jan 2 2006"
|
||||
_, err = time.Parse(layout, got)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("runLs(%#v, *FileInfo): mod time field mismatch, expected date layout %s, got: %#v, err: %#v", path, layout, got, err)
|
||||
}
|
||||
|
||||
// spacer
|
||||
got = result[55:56]
|
||||
if ok, err := regexp.MatchString("^\\s$", got); !ok {
|
||||
t.Errorf("runLs(%#v, *FileInfo): spacer 6 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
|
||||
}
|
||||
|
||||
// filename
|
||||
got = result[56:]
|
||||
if ok, err := regexp.MatchString("^"+path+"$", got); !ok {
|
||||
t.Errorf("runLs(%#v, *FileInfo): name field mismatch, expected examples, got: %#v, err: %#v", path, got, err)
|
||||
}
|
||||
}
|
||||
|
||||
func clientServerPair(t *testing.T) (*Client, *Server) {
|
||||
cr, sw := io.Pipe()
|
||||
sr, cw := io.Pipe()
|
||||
|
|
93
vendor/github.com/pkg/sftp/server_unix.go
generated
vendored
93
vendor/github.com/pkg/sftp/server_unix.go
generated
vendored
|
@ -11,96 +11,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func runLsTypeWord(dirent os.FileInfo) string {
|
||||
// find first character, the type char
|
||||
// b Block special file.
|
||||
// c Character special file.
|
||||
// d Directory.
|
||||
// l Symbolic link.
|
||||
// s Socket link.
|
||||
// p FIFO.
|
||||
// - Regular file.
|
||||
tc := '-'
|
||||
mode := dirent.Mode()
|
||||
if (mode & os.ModeDir) != 0 {
|
||||
tc = 'd'
|
||||
} else if (mode & os.ModeDevice) != 0 {
|
||||
tc = 'b'
|
||||
if (mode & os.ModeCharDevice) != 0 {
|
||||
tc = 'c'
|
||||
}
|
||||
} else if (mode & os.ModeSymlink) != 0 {
|
||||
tc = 'l'
|
||||
} else if (mode & os.ModeSocket) != 0 {
|
||||
tc = 's'
|
||||
} else if (mode & os.ModeNamedPipe) != 0 {
|
||||
tc = 'p'
|
||||
}
|
||||
|
||||
// owner
|
||||
orc := '-'
|
||||
if (mode & 0400) != 0 {
|
||||
orc = 'r'
|
||||
}
|
||||
owc := '-'
|
||||
if (mode & 0200) != 0 {
|
||||
owc = 'w'
|
||||
}
|
||||
oxc := '-'
|
||||
ox := (mode & 0100) != 0
|
||||
setuid := (mode & os.ModeSetuid) != 0
|
||||
if ox && setuid {
|
||||
oxc = 's'
|
||||
} else if setuid {
|
||||
oxc = 'S'
|
||||
} else if ox {
|
||||
oxc = 'x'
|
||||
}
|
||||
|
||||
// group
|
||||
grc := '-'
|
||||
if (mode & 040) != 0 {
|
||||
grc = 'r'
|
||||
}
|
||||
gwc := '-'
|
||||
if (mode & 020) != 0 {
|
||||
gwc = 'w'
|
||||
}
|
||||
gxc := '-'
|
||||
gx := (mode & 010) != 0
|
||||
setgid := (mode & os.ModeSetgid) != 0
|
||||
if gx && setgid {
|
||||
gxc = 's'
|
||||
} else if setgid {
|
||||
gxc = 'S'
|
||||
} else if gx {
|
||||
gxc = 'x'
|
||||
}
|
||||
|
||||
// all / others
|
||||
arc := '-'
|
||||
if (mode & 04) != 0 {
|
||||
arc = 'r'
|
||||
}
|
||||
awc := '-'
|
||||
if (mode & 02) != 0 {
|
||||
awc = 'w'
|
||||
}
|
||||
axc := '-'
|
||||
ax := (mode & 01) != 0
|
||||
sticky := (mode & os.ModeSticky) != 0
|
||||
if ax && sticky {
|
||||
axc = 't'
|
||||
} else if sticky {
|
||||
axc = 'T'
|
||||
} else if ax {
|
||||
axc = 'x'
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%c%c%c%c%c%c%c%c%c%c", tc, orc, owc, oxc, grc, gwc, gxc, arc, awc, axc)
|
||||
}
|
||||
|
||||
func runLsStatt(dirname string, dirent os.FileInfo, statt *syscall.Stat_t) string {
|
||||
func runLsStatt(dirent os.FileInfo, statt *syscall.Stat_t) string {
|
||||
// example from openssh sftp server:
|
||||
// crw-rw-rw- 1 root wheel 0 Jul 31 20:52 ttyvd
|
||||
// format:
|
||||
|
@ -136,7 +47,7 @@ func runLs(dirname string, dirent os.FileInfo) string {
|
|||
if dsys == nil {
|
||||
} else if statt, ok := dsys.(*syscall.Stat_t); !ok {
|
||||
} else {
|
||||
return runLsStatt(dirname, dirent, statt)
|
||||
return runLsStatt(dirent, statt)
|
||||
}
|
||||
|
||||
return path.Join(dirname, dirent.Name())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue