smallweb
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258package sftp
import ( "errors" "fmt" "io" "os" "path/filepath" "strings"
"time"
"github.com/charmbracelet/ssh" "github.com/charmbracelet/wish" "github.com/pkg/sftp")
// Based on https://github.com/pkg/sftp/blob/master/request-example.go
type handler struct { session ssh.Session root *os.Root}
func (h *handler) Filecmd(r *sftp.Request) error { switch r.Method { case "Rename": if _, err := h.root.Stat(strings.TrimPrefix(r.Filepath, "/")); err != nil { return err }
src := filepath.Join(h.root.Name(), strings.TrimPrefix(r.Filepath, "/")) dst := filepath.Join(h.root.Name(), strings.TrimPrefix(r.Target, "/")) return os.Rename(src, dst) case "Link": if _, err := h.root.Stat(strings.TrimPrefix(r.Filepath, "/")); err != nil { return err }
if _, err := h.root.Stat(strings.TrimPrefix(r.Target, "/")); err == nil { return fmt.Errorf("target file exists") } else if !errors.Is(err, os.ErrNotExist) { return err }
src := filepath.Join(h.root.Name(), strings.TrimPrefix(r.Filepath, "/")) dst := filepath.Join(h.root.Name(), strings.TrimPrefix(r.Target, "/")) return os.Link(src, dst) case "Symlink": if _, err := h.root.Stat(strings.TrimPrefix(r.Filepath, "/")); err != nil { return fmt.Errorf("file does not exist") }
if _, err := h.root.Stat(strings.TrimPrefix(r.Target, "/")); err == nil { return fmt.Errorf("target file exists") } else if !errors.Is(err, os.ErrNotExist) { return err }
src := filepath.Join(h.root.Name(), strings.TrimPrefix(r.Filepath, "/")) dst := filepath.Join(h.root.Name(), strings.TrimPrefix(r.Target, "/")) return os.Symlink(src, dst) case "Rmdir": return h.root.Remove(strings.TrimPrefix(r.Filepath, "/")) case "Remove": return h.root.Remove(strings.TrimPrefix(r.Filepath, "/")) case "Mkdir": return h.root.Mkdir(strings.TrimPrefix(r.Filepath, "/"), 0777) case "Setstat": if _, err := h.root.Stat(strings.TrimPrefix(r.Filepath, "/")); err != nil { return fmt.Errorf("file does not exist") }
fp := filepath.Join(h.root.Name(), strings.TrimPrefix(r.Filepath, "/")) attrs := r.Attributes()
if attrs.Size != 0 { if err := os.Truncate(fp, int64(attrs.Size)); err != nil { return err } }
if attrs.Mode != 0 { if err := os.Chmod(fp, os.FileMode(attrs.Mode)); err != nil { return err } }
if attrs.GID != 0 || attrs.UID != 0 { if err := os.Chown(fp, int(attrs.UID), int(attrs.GID)); err != nil { return err } }
if attrs.Atime != 0 && attrs.Mtime != 0 { atime := time.Unix(int64(attrs.Atime), 0) mtime := time.Unix(int64(attrs.Mtime), 0) if err := os.Chtimes(fp, atime, mtime); err != nil { return err } }
return nil } return errors.New("unsupported")}
func (h *handler) Filelist(r *sftp.Request) (sftp.ListerAt, error) { switch r.Method { case "List": if r.Filepath == "/" { entries, err := os.ReadDir(h.root.Name()) if err != nil { return nil, err }
var fileInfos []os.FileInfo for _, entry := range entries { fileInfo, err := entry.Info() if err != nil { return nil, err } fileInfos = append(fileInfos, fileInfo) }
return listerat(fileInfos), nil }
f, err := h.root.Open(strings.TrimPrefix(r.Filepath, "/")) if err != nil { return nil, err }
entries, err := f.ReadDir(0) if err != nil { return nil, err }
var fileInfos []os.FileInfo for _, entry := range entries { fileInfo, err := entry.Info() if err != nil { return nil, err } fileInfos = append(fileInfos, fileInfo) }
return listerat(fileInfos), nil
case "Stat": if r.Filepath == "/" { stat, err := os.Stat(h.root.Name()) if err != nil { return nil, err }
return listerat([]os.FileInfo{stat}), nil }
info, err := h.root.Stat(strings.TrimPrefix(r.Filepath, "/")) if err != nil { return nil, err }
return listerat([]os.FileInfo{info}), nil }
return nil, errors.New("unsupported")}
func (h *handler) Filewrite(r *sftp.Request) (io.WriterAt, error) { f, err := h.root.OpenFile(strings.TrimPrefix(r.Filepath, "/"), flag(r.Pflags()), 0644) if err != nil { return nil, err }
return f, nil}
func flag(pflags sftp.FileOpenFlags) int { var flag int if pflags.Creat { flag |= os.O_CREATE } if pflags.Trunc { flag |= os.O_TRUNC } if pflags.Excl { flag |= os.O_EXCL } if pflags.Write { flag |= os.O_WRONLY } if pflags.Read { flag |= os.O_RDONLY } if pflags.Append { flag |= os.O_APPEND }
return flag}
func (h *handler) Fileread(r *sftp.Request) (io.ReaderAt, error) { if r.Filepath == "/" { return nil, os.ErrInvalid }
return h.root.Open(strings.TrimPrefix(r.Filepath, "/"))}
type handlererr struct { Handler *handler}
func (f *handlererr) Filecmd(r *sftp.Request) error { err := f.Handler.Filecmd(r) if err != nil { wish.Errorln(f.Handler.session, err) } return err}func (f *handlererr) Filelist(r *sftp.Request) (sftp.ListerAt, error) { result, err := f.Handler.Filelist(r) if err != nil { wish.Errorln(f.Handler.session, err) } return result, err}func (f *handlererr) Filewrite(r *sftp.Request) (io.WriterAt, error) { result, err := f.Handler.Filewrite(r) if err != nil { wish.Errorln(f.Handler.session, err) } return result, err}func (f *handlererr) Fileread(r *sftp.Request) (io.ReaderAt, error) { result, err := f.Handler.Fileread(r) if err != nil { wish.Errorln(f.Handler.session, err) } return result, err}
type listerat []os.FileInfo
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}