| 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 |
| } |
| } |