blob: 1d8643a4b717915cbff465404379a4caf19be071 [file] [log] [blame]
package sftp
import (
"io"
"os"
"path"
"path/filepath"
"syscall"
)
type Request struct {
// Get, Put, SetStat, Stat, Rename, Remove
// Rmdir, Mkdir, List, Readlink, Symlink
Method string
Filepath string
Pflags uint32
Attrs []byte // convert to sub-struct
Target string // for renames and sym-links
// packet data
pkt_id uint32
data []byte
length uint32
// reader/writer from handlers
put_writer io.Writer
get_reader io.Reader
eof bool // hack for readdir to keep eof state
}
// Here mainly to specify that Filepath is required
func newRequest(path string) *Request {
request := &Request{Filepath: filepath.Clean(path)}
return request
}
// called from worker to handle packet/request
func (r *Request) handle(handlers Handlers) (resp_packet, error) {
var err error
var rpkt resp_packet
switch r.Method {
case "Get":
rpkt, err = fileget(handlers.FileGet, r)
case "Put":
rpkt, err = fileput(handlers.FilePut, r)
case "SetStat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove":
rpkt, err = filecmd(handlers.FileCmd, r)
case "List", "Stat", "Readlink":
rpkt, err = fileinfo(handlers.FileInfo, r)
}
return rpkt, err
}
// wrap FileReader handler
func fileget(h FileReader, r *Request) (resp_packet, error) {
if r.get_reader == nil {
reader, err := h.Fileread(r)
if err != nil { return nil, syscall.EBADF }
r.get_reader = reader
}
reader := r.get_reader
data := make([]byte, clamp(r.length, maxTxPacket))
n, err := reader.Read(data)
if err != nil && (err != io.EOF || n == 0) { return nil, err }
return &sshFxpDataPacket{
ID: r.pkt_id,
Length: uint32(n),
Data: data[:n],
}, nil
}
// wrap FileWriter handler
func fileput(h FileWriter, r *Request) (resp_packet, error) {
if r.put_writer == nil {
writer, err := h.Filewrite(r)
if err != nil { return nil, syscall.EBADF }
r.put_writer = writer
}
writer := r.put_writer
_, err := writer.Write(r.data)
if err != nil { return nil, err }
return &sshFxpStatusPacket{
ID: r.pkt_id,
StatusError: StatusError{
Code: ssh_FX_OK,
}}, nil
}
// wrap FileCmder handler
func filecmd(h FileCmder, r *Request) (resp_packet, error) {
err := h.Filecmd(r)
if err != nil { return nil, err }
return &sshFxpStatusPacket{
ID: r.pkt_id,
StatusError: StatusError{
Code: ssh_FX_OK,
}}, nil
}
// wrap FileInfoer handler
func fileinfo(h FileInfoer, r *Request) (resp_packet, error) {
if r.eof { return nil, io.EOF }
finfo, err := h.Fileinfo(r)
if err != nil { return nil, err }
switch r.Method {
case "List":
dirname := path.Base(r.Filepath)
ret := &sshFxpNamePacket{ID: r.pkt_id}
for _, fi := range finfo {
ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
Name: fi.Name(),
LongName: runLs(dirname, fi),
Attrs: []interface{}{fi},
})
}
r.eof = true
return ret, nil
case "Stat":
if len(finfo) == 0 {
err = &os.PathError{"stat", r.Filepath, syscall.ENOENT}
return nil, err
}
return &sshFxpStatResponse{
ID: r.pkt_id,
info: finfo[0],
}, nil
case "Readlink":
if len(finfo) == 0 {
err = &os.PathError{"readlink", r.Filepath, syscall.ENOENT}
return nil, err
}
filename := filepath.Clean(r.Filepath)
return &sshFxpNamePacket{
ID: r.pkt_id,
NameAttrs: []sshFxpNameAttr{{
Name: filename,
LongName: filename,
Attrs: emptyFileStat,
}},
}, nil
}
return nil, err
}
// populate attributes of request object from packet data
func (r *Request) populate(p interface{}) {
// r.Filepath should already be set
switch p := p.(type) {
case *sshFxpSetstatPacket:
r.Method = "Setstat"
r.Pflags = p.Flags
r.Attrs = p.Attrs.([]byte)
r.pkt_id = p.id()
case *sshFxpFsetstatPacket:
r.Method = "Setstat"
r.Pflags = p.Flags
r.Attrs = p.Attrs.([]byte)
r.pkt_id = p.id()
case *sshFxpRenamePacket:
r.Method = "Rename"
r.Target = filepath.Clean(p.Newpath)
r.pkt_id = p.id()
case *sshFxpSymlinkPacket:
r.Method = "Symlink"
r.Target = filepath.Clean(p.Linkpath)
r.pkt_id = p.id()
case *sshFxpReadPacket:
r.Method = "Get"
r.length = p.Len
r.pkt_id = p.id()
case *sshFxpWritePacket:
r.Method = "Put"
r.data = p.Data
r.length = p.Length
r.pkt_id = p.id()
case *sshFxpReaddirPacket:
r.Method = "List"
r.pkt_id = p.id()
case *sshFxpRemovePacket:
r.Method = "Remove"
r.pkt_id = p.id()
case *sshFxpStatPacket, *sshFxpLstatPacket, *sshFxpFstatPacket:
r.Method = "Stat"
r.pkt_id = p.(packet).id()
case *sshFxpRmdirPacket:
r.Method = "Rmdir"
r.pkt_id = p.id()
case *sshFxpReadlinkPacket:
r.Method = "Readlink"
r.pkt_id = p.id()
case *sshFxpMkdirPacket:
r.Method = "Mkdir"
r.pkt_id = p.id()
//r.Attrs are ignored in ./packet.go
}
}