- commit
- cd46782
- parent
- 2a2904f
- author
- Eric Bower
- date
- 2023-11-26 19:37:41 +0000 UTC
refactor: move `wish/*` to its own repo (#62)
+7,
-7
1@@ -19,13 +19,13 @@ import (
2 "github.com/picosh/pico/shared"
3 "github.com/picosh/pico/shared/storage"
4 "github.com/picosh/pico/wish/cms"
5- "github.com/picosh/pico/wish/list"
6- "github.com/picosh/pico/wish/pipe"
7- "github.com/picosh/pico/wish/proxy"
8- "github.com/picosh/pico/wish/send/auth"
9- wishrsync "github.com/picosh/pico/wish/send/rsync"
10- "github.com/picosh/pico/wish/send/scp"
11- "github.com/picosh/pico/wish/send/sftp"
12+ "github.com/picosh/send/list"
13+ "github.com/picosh/send/pipe"
14+ "github.com/picosh/send/proxy"
15+ "github.com/picosh/send/send/auth"
16+ wishrsync "github.com/picosh/send/send/rsync"
17+ "github.com/picosh/send/send/scp"
18+ "github.com/picosh/send/send/sftp"
19 )
20
21 type SSHServer struct{}
+8,
-8
1@@ -19,14 +19,14 @@ import (
2 "github.com/picosh/pico/shared"
3 "github.com/picosh/pico/shared/storage"
4 "github.com/picosh/pico/wish/cms"
5- "github.com/picosh/pico/wish/list"
6- "github.com/picosh/pico/wish/pipe"
7- "github.com/picosh/pico/wish/proxy"
8- "github.com/picosh/pico/wish/send/auth"
9- wishrsync "github.com/picosh/pico/wish/send/rsync"
10- "github.com/picosh/pico/wish/send/scp"
11- "github.com/picosh/pico/wish/send/sftp"
12- "github.com/picosh/pico/wish/send/utils"
13+ "github.com/picosh/send/list"
14+ "github.com/picosh/send/pipe"
15+ "github.com/picosh/send/proxy"
16+ "github.com/picosh/send/send/auth"
17+ wishrsync "github.com/picosh/send/send/rsync"
18+ "github.com/picosh/send/send/scp"
19+ "github.com/picosh/send/send/sftp"
20+ "github.com/picosh/send/send/utils"
21 )
22
23 type SSHServer struct{}
+7,
-7
1@@ -19,13 +19,13 @@ import (
2 "github.com/picosh/pico/shared"
3 "github.com/picosh/pico/shared/storage"
4 "github.com/picosh/pico/wish/cms"
5- "github.com/picosh/pico/wish/list"
6- "github.com/picosh/pico/wish/pipe"
7- "github.com/picosh/pico/wish/proxy"
8- "github.com/picosh/pico/wish/send/auth"
9- wishrsync "github.com/picosh/pico/wish/send/rsync"
10- "github.com/picosh/pico/wish/send/scp"
11- "github.com/picosh/pico/wish/send/sftp"
12+ "github.com/picosh/send/list"
13+ "github.com/picosh/send/pipe"
14+ "github.com/picosh/send/proxy"
15+ "github.com/picosh/send/send/auth"
16+ wishrsync "github.com/picosh/send/send/rsync"
17+ "github.com/picosh/send/send/scp"
18+ "github.com/picosh/send/send/sftp"
19 )
20
21 type SSHServer struct{}
+7,
-7
1@@ -19,13 +19,13 @@ import (
2 "github.com/picosh/pico/shared"
3 "github.com/picosh/pico/shared/storage"
4 "github.com/picosh/pico/wish/cms"
5- "github.com/picosh/pico/wish/list"
6- "github.com/picosh/pico/wish/pipe"
7- "github.com/picosh/pico/wish/proxy"
8- "github.com/picosh/pico/wish/send/auth"
9- wishrsync "github.com/picosh/pico/wish/send/rsync"
10- "github.com/picosh/pico/wish/send/scp"
11- "github.com/picosh/pico/wish/send/sftp"
12+ "github.com/picosh/send/list"
13+ "github.com/picosh/send/pipe"
14+ "github.com/picosh/send/proxy"
15+ "github.com/picosh/send/send/auth"
16+ wishrsync "github.com/picosh/send/send/rsync"
17+ "github.com/picosh/send/send/scp"
18+ "github.com/picosh/send/send/sftp"
19 )
20
21 type SSHServer struct{}
+1,
-1
1@@ -7,7 +7,7 @@ import (
2 "strings"
3
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 )
8
9 func (h *UploadAssetHandler) validateAsset(data *FileData) (bool, error) {
+2,
-2
1@@ -14,7 +14,7 @@ import (
2 "github.com/picosh/pico/shared"
3 "github.com/picosh/pico/shared/storage"
4 "github.com/picosh/pico/wish/cms/util"
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 "go.uber.org/zap"
8 )
9
10@@ -105,7 +105,7 @@ func (h *UploadAssetHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.Fil
11 fileInfo.FSize = size
12 fileInfo.FModTime = modTime
13
14- reader := utils.NewAllReaderAt(contents)
15+ reader := shared.NewAllReaderAt(contents)
16
17 return fileInfo, reader, nil
18 }
+1,
-1
1@@ -6,7 +6,7 @@ import (
2 "github.com/picosh/pico/imgs"
3 "github.com/picosh/pico/shared"
4 "github.com/picosh/pico/shared/storage"
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 )
8
9 type ImgsAPI struct {
+2,
-2
1@@ -17,7 +17,7 @@ import (
2 "github.com/picosh/pico/shared"
3 "github.com/picosh/pico/shared/storage"
4 "github.com/picosh/pico/wish/cms/util"
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 "go.uber.org/zap"
8 )
9
10@@ -112,7 +112,7 @@ func (h *UploadImgHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileI
11 return nil, nil, err
12 }
13
14- reader := utils.NewAllReaderAt(contents)
15+ reader := shared.NewAllReaderAt(contents)
16
17 return fileInfo, reader, nil
18 }
+1,
-1
1@@ -9,7 +9,7 @@ import (
2 "github.com/charmbracelet/ssh"
3 "github.com/picosh/pico/db"
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 )
8
9 func (h *UploadImgHandler) validateImg(data *PostMetaData) (bool, error) {
+1,
-1
1@@ -16,7 +16,7 @@ import (
2 "github.com/picosh/pico/shared"
3 "github.com/picosh/pico/shared/storage"
4 "github.com/picosh/pico/wish/cms/util"
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 "go.uber.org/zap"
8 )
9
M
go.mod
+4,
-4
1@@ -1,10 +1,9 @@
2 module github.com/picosh/pico
3
4-go 1.21
5+go 1.21.4
6
7 require (
8 github.com/alecthomas/chroma v0.10.0
9- github.com/antoniomika/go-rsync-receiver v0.0.0-20231110145728-c94949e1ab7d
10 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
11 github.com/charmbracelet/bubbles v0.16.1
12 github.com/charmbracelet/bubbletea v0.24.2
13@@ -15,7 +14,6 @@ require (
14 github.com/google/go-cmp v0.6.0
15 github.com/gorilla/feeds v1.1.2
16 github.com/lib/pq v1.10.9
17- github.com/matryer/is v1.4.1
18 github.com/microcosm-cc/bluemonday v1.0.26
19 github.com/minio/madmin-go/v3 v3.0.29
20 github.com/minio/minio-go/v7 v7.0.63
21@@ -23,7 +21,7 @@ require (
22 github.com/muesli/reflow v0.3.0
23 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577
24 github.com/patrickmn/go-cache v2.1.0+incompatible
25- github.com/pkg/sftp v1.13.6
26+ github.com/picosh/send v0.0.0-20231126163457-97725d2b2be1
27 github.com/sendgrid/sendgrid-go v3.13.0+incompatible
28 github.com/yuin/goldmark v1.6.0
29 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
30@@ -39,6 +37,7 @@ require (
31 github.com/PuerkitoBio/goquery v1.8.1 // indirect
32 github.com/andybalholm/cascadia v1.3.2 // indirect
33 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
34+ github.com/antoniomika/go-rsync-receiver v0.0.0-20231110145728-c94949e1ab7d // indirect
35 github.com/atotto/clipboard v0.1.4 // indirect
36 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
37 github.com/aymerick/douceur v0.2.0 // indirect
38@@ -88,6 +87,7 @@ require (
39 github.com/muesli/termenv v0.15.2 // indirect
40 github.com/neurosnap/go-jpeg-image-structure v0.0.0-20221010133817-70b1c1ff679e // indirect
41 github.com/philhofer/fwd v1.1.2 // indirect
42+ github.com/pkg/sftp v1.13.6 // indirect
43 github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
44 github.com/prometheus/client_golang v1.17.0 // indirect
45 github.com/prometheus/client_model v0.5.0 // indirect
M
go.sum
+2,
-0
1@@ -182,6 +182,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
2 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
3 github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
4 github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
5+github.com/picosh/send v0.0.0-20231126163457-97725d2b2be1 h1:CsRnWhDufv7yiLgbClZGvWk/iFRB/8Uzo8jypLfY8J8=
6+github.com/picosh/send v0.0.0-20231126163457-97725d2b2be1/go.mod h1:3N0Z4Z8367ikx3v14CVU8HCwNXTwz+Brt+GzYE9i8wU=
7 github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
8 github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
9 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+1,
-1
1@@ -1,7 +1,7 @@
2 package imgs
3
4 import (
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 )
8
9 type IImgsAPI interface {
+7,
-7
1@@ -18,13 +18,13 @@ import (
2 "github.com/picosh/pico/shared"
3 "github.com/picosh/pico/shared/storage"
4 "github.com/picosh/pico/wish/cms"
5- "github.com/picosh/pico/wish/list"
6- "github.com/picosh/pico/wish/pipe"
7- "github.com/picosh/pico/wish/proxy"
8- "github.com/picosh/pico/wish/send/auth"
9- wishrsync "github.com/picosh/pico/wish/send/rsync"
10- "github.com/picosh/pico/wish/send/scp"
11- "github.com/picosh/pico/wish/send/sftp"
12+ "github.com/picosh/send/list"
13+ "github.com/picosh/send/pipe"
14+ "github.com/picosh/send/proxy"
15+ "github.com/picosh/send/send/auth"
16+ wishrsync "github.com/picosh/send/send/rsync"
17+ "github.com/picosh/send/send/scp"
18+ "github.com/picosh/send/send/sftp"
19 )
20
21 type SSHServer struct{}
+1,
-1
1@@ -19,7 +19,7 @@ import (
2 "github.com/picosh/pico/db/postgres"
3 "github.com/picosh/pico/shared"
4 "github.com/picosh/pico/shared/storage"
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 "go.uber.org/zap"
8 )
9
+7,
-7
1@@ -17,13 +17,13 @@ import (
2 uploadassets "github.com/picosh/pico/filehandlers/assets"
3 "github.com/picosh/pico/shared"
4 "github.com/picosh/pico/shared/storage"
5- "github.com/picosh/pico/wish/list"
6- "github.com/picosh/pico/wish/pipe"
7- "github.com/picosh/pico/wish/proxy"
8- "github.com/picosh/pico/wish/send/auth"
9- wishrsync "github.com/picosh/pico/wish/send/rsync"
10- "github.com/picosh/pico/wish/send/scp"
11- "github.com/picosh/pico/wish/send/sftp"
12+ "github.com/picosh/send/list"
13+ "github.com/picosh/send/pipe"
14+ "github.com/picosh/send/proxy"
15+ "github.com/picosh/send/send/auth"
16+ wishrsync "github.com/picosh/send/send/rsync"
17+ "github.com/picosh/send/send/scp"
18+ "github.com/picosh/send/send/sftp"
19 )
20
21 type SSHServer struct{}
+1,
-1
1@@ -9,7 +9,7 @@ import (
2 "github.com/picosh/pico/db"
3 uploadassets "github.com/picosh/pico/filehandlers/assets"
4 "github.com/picosh/pico/wish/cms/util"
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 )
8
9 func getUser(s ssh.Session, dbpool db.DB) (*db.User, error) {
1@@ -6,7 +6,7 @@ import (
2 "path/filepath"
3 "strings"
4
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 )
8
9 func GetAssetBucketName(userID string) string {
1@@ -1,4 +1,4 @@
2-package utils
3+package shared
4
5 import (
6 "errors"
7@@ -6,33 +6,14 @@ import (
8 "net/http"
9
10 "github.com/minio/minio-go/v7"
11+ "github.com/picosh/send/send/utils"
12 )
13
14-type ReadAndReaderAt interface {
15- io.ReaderAt
16- io.Reader
17-}
18-
19-type ReaderAtCloser interface {
20- io.ReaderAt
21- io.ReadCloser
22-}
23-
24-func NopReaderAtCloser(r ReadAndReaderAt) ReaderAtCloser {
25- return nopReaderAtCloser{r}
26-}
27-
28-type nopReaderAtCloser struct {
29- ReadAndReaderAt
30-}
31-
32-func (nopReaderAtCloser) Close() error { return nil }
33-
34 type AllReaderAt struct {
35- Reader ReaderAtCloser
36+ Reader utils.ReaderAtCloser
37 }
38
39-func NewAllReaderAt(reader ReaderAtCloser) *AllReaderAt {
40+func NewAllReaderAt(reader utils.ReaderAtCloser) *AllReaderAt {
41 return &AllReaderAt{reader}
42 }
43
1@@ -10,7 +10,7 @@ import (
2 "strings"
3 "time"
4
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 )
8
9 // https://stackoverflow.com/a/32482941
1@@ -15,7 +15,7 @@ import (
2 "github.com/minio/madmin-go/v3"
3 "github.com/minio/minio-go/v7"
4 "github.com/minio/minio-go/v7/pkg/credentials"
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 )
8
9 type StorageMinio struct {
1@@ -5,7 +5,7 @@ import (
2 "os"
3 "time"
4
5- "github.com/picosh/pico/wish/send/utils"
6+ "github.com/picosh/send/send/utils"
7 )
8
9 type Bucket struct {
+0,
-69
1@@ -1,69 +0,0 @@
2-package main
3-
4-import (
5- "fmt"
6- "log"
7- "os"
8- "strings"
9- "time"
10-
11- "github.com/charmbracelet/ssh"
12- "github.com/charmbracelet/wish"
13- "github.com/picosh/pico/wish/send"
14- "github.com/picosh/pico/wish/send/utils"
15- "go.uber.org/zap"
16-)
17-
18-type handler struct {
19-}
20-
21-func (h *handler) GetLogger() *zap.SugaredLogger {
22- return zap.NewNop().Sugar()
23-}
24-
25-func (h *handler) Write(session ssh.Session, file *utils.FileEntry) (string, error) {
26- str := fmt.Sprintf("Received file: %+v from session: %+v", file, session)
27- log.Print(str)
28- return str, nil
29-}
30-
31-func (h *handler) Validate(session ssh.Session) error {
32- log.Printf("Received validate from session: %+v", session)
33-
34- return nil
35-}
36-
37-func (h *handler) Read(session ssh.Session, entry *utils.FileEntry) (os.FileInfo, utils.ReaderAtCloser, error) {
38- log.Printf("Received validate from session: %+v", session)
39-
40- data := strings.NewReader("lorem ipsum dolor")
41-
42- return &utils.VirtualFile{
43- FName: "test",
44- FIsDir: false,
45- FSize: data.Size(),
46- FModTime: time.Now(),
47- }, utils.NopReaderAtCloser(data), nil
48-}
49-
50-func (h *handler) List(session ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
51- return nil, nil
52-}
53-
54-func main() {
55- h := &handler{}
56-
57- s, err := wish.NewServer(
58- wish.WithAddress("localhost:9000"),
59- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
60- send.Middleware(h),
61- )
62-
63- if err != nil {
64- log.Fatal(err)
65- }
66-
67- log.Println("Starting ssh server on 9000")
68-
69- log.Fatal(s.ListenAndServe())
70-}
+0,
-45
1@@ -1,45 +0,0 @@
2-package list
3-
4-import (
5- "sort"
6- "strings"
7-
8- "github.com/charmbracelet/ssh"
9- "github.com/charmbracelet/wish"
10- "github.com/picosh/pico/wish/send/utils"
11-)
12-
13-func Middleware(writeHandler utils.CopyFromClientHandler) wish.Middleware {
14- return func(sshHandler ssh.Handler) ssh.Handler {
15- return func(session ssh.Session) {
16- cmd := session.Command()
17- if !(len(cmd) > 1 && cmd[0] == "command" && cmd[1] == "ls") {
18- sshHandler(session)
19- return
20- }
21-
22- fileList, err := writeHandler.List(session, "/", true, false)
23- if err != nil {
24- utils.ErrorHandler(session, err)
25- return
26- }
27-
28- var data []string
29- for _, file := range fileList {
30- name := strings.ReplaceAll(file.Name(), "/", "")
31- if file.IsDir() {
32- name += "/"
33- }
34-
35- data = append(data, name)
36- }
37-
38- sort.Strings(data)
39-
40- _, err = session.Write([]byte(strings.Join(data, "\r\n")))
41- if err != nil {
42- utils.ErrorHandler(session, err)
43- }
44- }
45- }
46-}
+0,
-65
1@@ -1,65 +0,0 @@
2-package pipe
3-
4-import (
5- "fmt"
6- "io/fs"
7- "strconv"
8- "strings"
9- "time"
10-
11- "github.com/charmbracelet/ssh"
12- "github.com/charmbracelet/wish"
13- "github.com/picosh/pico/wish/send/utils"
14-)
15-
16-func Middleware(writeHandler utils.CopyFromClientHandler, ext string) wish.Middleware {
17- return func(sshHandler ssh.Handler) ssh.Handler {
18- return func(session ssh.Session) {
19- _, _, activePty := session.Pty()
20- if activePty {
21- _ = session.Exit(0)
22- _ = session.Close()
23- return
24- }
25-
26- cmd := session.Command()
27-
28- name := ""
29- if len(cmd) > 0 {
30- name = strings.TrimSpace(cmd[0])
31- if strings.Contains(name, "=") {
32- name = ""
33- }
34- }
35-
36- postTime := time.Now()
37-
38- if name == "" {
39- name = fmt.Sprintf("%s%s", strconv.Itoa(int(postTime.UnixNano())), ext)
40- }
41-
42- result, err := writeHandler.Write(session, &utils.FileEntry{
43- Filepath: name,
44- Mode: fs.FileMode(0777),
45- Size: 0,
46- Mtime: postTime.Unix(),
47- Atime: postTime.Unix(),
48- Reader: session,
49- })
50- if err != nil {
51- utils.ErrorHandler(session, err)
52- return
53- }
54-
55- if result != "" {
56- _, err = session.Write([]byte(fmt.Sprintf("%s\r\n", result)))
57- if err != nil {
58- utils.ErrorHandler(session, err)
59- }
60- return
61- }
62-
63- sshHandler(session)
64- }
65- }
66-}
+0,
-31
1@@ -1,31 +0,0 @@
2-package proxy
3-
4-import (
5- "github.com/charmbracelet/ssh"
6- "github.com/charmbracelet/wish"
7-)
8-
9-type Router func(sh ssh.Handler, s ssh.Session) []wish.Middleware
10-
11-func withMiddleware(mdw ...wish.Middleware) ssh.Handler {
12- handler := func(s ssh.Session) {}
13- for _, mw := range mdw {
14- handler = mw(handler)
15- }
16- return handler
17-}
18-
19-func WithProxy(router Router, otherMiddleware ...wish.Middleware) ssh.Option {
20- mdw := func(sh ssh.Handler) ssh.Handler {
21- return func(s ssh.Session) {
22- mw := router(sh, s)
23- fn := withMiddleware(mw...)
24- fn(s)
25- }
26- }
27-
28- newMiddleware := []wish.Middleware{mdw}
29- newMiddleware = append(newMiddleware, otherMiddleware...)
30-
31- return wish.WithMiddleware(newMiddleware...)
32-}
+0,
-28
1@@ -1,28 +0,0 @@
2-package auth
3-
4-import (
5- "github.com/charmbracelet/ssh"
6- "github.com/charmbracelet/wish"
7- "github.com/picosh/pico/wish/send/utils"
8-)
9-
10-func Middleware(writeHandler utils.CopyFromClientHandler) wish.Middleware {
11- return func(sshHandler ssh.Handler) ssh.Handler {
12- return func(session ssh.Session) {
13- defer func() {
14- if r := recover(); r != nil {
15- writeHandler.GetLogger().Error("error running auth middleware: ", r)
16- _, _ = session.Stderr().Write([]byte("error running auth middleware\r\n"))
17- }
18- }()
19-
20- err := writeHandler.Validate(session)
21- if err != nil {
22- utils.ErrorHandler(session, err)
23- return
24- }
25-
26- sshHandler(session)
27- }
28- }
29-}
+0,
-238
1@@ -1,238 +0,0 @@
2-package rsync
3-
4-import (
5- "errors"
6- "fmt"
7- "io"
8- "io/fs"
9- "os"
10- "path"
11- "slices"
12- "strings"
13-
14- "github.com/antoniomika/go-rsync-receiver/rsyncreceiver"
15- "github.com/antoniomika/go-rsync-receiver/rsyncsender"
16- rsyncutils "github.com/antoniomika/go-rsync-receiver/utils"
17- "github.com/charmbracelet/ssh"
18- "github.com/charmbracelet/wish"
19- "github.com/picosh/pico/wish/send/utils"
20-)
21-
22-type handler struct {
23- session ssh.Session
24- writeHandler utils.CopyFromClientHandler
25- root string
26- recursive bool
27- ignoreTimes bool
28-}
29-
30-func (h *handler) Skip(file *rsyncutils.ReceiverFile) bool {
31- if file.FileMode().IsDir() {
32- return true
33- }
34-
35- fI, _, err := h.writeHandler.Read(h.session, &utils.FileEntry{Filepath: path.Join("/", h.root, file.Name)})
36- if err == nil && fI.ModTime().Equal(file.ModTime) && file.Length == fI.Size() {
37- return true
38- }
39-
40- return false
41-}
42-
43-func (h *handler) List(rPath string) ([]fs.FileInfo, error) {
44- isDir := false
45- if rPath == "." {
46- rPath = "/"
47- isDir = true
48- }
49-
50- list, err := h.writeHandler.List(h.session, rPath, isDir, h.recursive)
51- if err != nil {
52- return nil, err
53- }
54-
55- var dirs []string
56-
57- var newList []fs.FileInfo
58-
59- for _, f := range list {
60- if !f.IsDir() && f.Size() == 0 {
61- continue
62- }
63-
64- fname := f.Name()
65- if strings.HasPrefix(f.Name(), "/") {
66- fname = path.Join(rPath, f.Name())
67- }
68-
69- if fname == "" && !f.IsDir() {
70- fname = path.Base(rPath)
71- }
72-
73- newFile := &utils.VirtualFile{
74- FName: fname,
75- FIsDir: f.IsDir(),
76- FSize: f.Size(),
77- FModTime: f.ModTime(),
78- FSys: f.Sys(),
79- }
80-
81- newList = append(newList, newFile)
82-
83- parts := strings.Split(newFile.Name(), string(os.PathSeparator))
84- lastDir := newFile.Name()
85- for i := 0; i < len(parts); i++ {
86- lastDir, _ = path.Split(lastDir)
87- if lastDir == "" {
88- continue
89- }
90-
91- lastDir = lastDir[:len(lastDir)-1]
92- dirs = append(dirs, lastDir)
93- }
94- }
95-
96- for _, dir := range dirs {
97- newList = append(newList, &utils.VirtualFile{
98- FName: dir,
99- FIsDir: true,
100- })
101- }
102-
103- slices.Reverse(newList)
104-
105- onlyEmpty := true
106- for _, f := range newList {
107- if f.Name() != "" {
108- onlyEmpty = false
109- }
110- }
111-
112- if len(newList) == 0 || onlyEmpty {
113- return nil, errors.New("no files to send, the directory may not exist or could be empty")
114- }
115-
116- return newList, nil
117-}
118-
119-func (h *handler) Read(file *rsyncutils.SenderFile) (os.FileInfo, io.ReaderAt, error) {
120- filePath := file.WPath
121-
122- if strings.HasSuffix(h.root, file.WPath) {
123- filePath = h.root
124- } else if !strings.HasPrefix(filePath, h.root) {
125- filePath = path.Join(h.root, file.Path, file.WPath)
126- }
127-
128- return h.writeHandler.Read(h.session, &utils.FileEntry{Filepath: filePath})
129-}
130-
131-func (h *handler) Put(file *rsyncutils.ReceiverFile) (int64, error) {
132- fileEntry := &utils.FileEntry{
133- Filepath: path.Join("/", h.root, file.Name),
134- Mode: fs.FileMode(0600),
135- Size: file.Length,
136- Mtime: file.ModTime.Unix(),
137- Atime: file.ModTime.Unix(),
138- }
139- fileEntry.Reader = file.Buf
140-
141- msg, err := h.writeHandler.Write(h.session, fileEntry)
142- if err != nil {
143- errMsg := fmt.Sprintf("%s\r\n", err.Error())
144- _, err = h.session.Stderr().Write([]byte(errMsg))
145- }
146- if msg != "" {
147- nMsg := fmt.Sprintf("%s\r\n", msg)
148- _, err = h.session.Stderr().Write([]byte(nMsg))
149- }
150- return 0, err
151-}
152-
153-func Middleware(writeHandler utils.CopyFromClientHandler) wish.Middleware {
154- return func(sshHandler ssh.Handler) ssh.Handler {
155- return func(session ssh.Session) {
156- defer func() {
157- if r := recover(); r != nil {
158- writeHandler.GetLogger().Error("error running rsync middleware: ", r)
159- _, _ = session.Stderr().Write([]byte("error running rsync middleware, check the flags you are using\r\n"))
160- }
161- }()
162-
163- cmd := session.Command()
164- if len(cmd) == 0 || cmd[0] != "rsync" {
165- sshHandler(session)
166- return
167- }
168-
169- fileHandler := &handler{
170- session: session,
171- writeHandler: writeHandler,
172- root: strings.TrimPrefix(cmd[len(cmd)-1], "/"),
173- }
174-
175- cmdFlags := session.Command()
176-
177- for _, arg := range cmd {
178- if arg == "--sender" {
179- opts, parser := rsyncsender.NewGetOpt()
180-
181- compress := parser.Bool("z", false)
182-
183- _, _ = parser.Parse(cmdFlags[1:])
184-
185- fileHandler.recursive = opts.Recurse
186- fileHandler.ignoreTimes = opts.IgnoreTimes
187-
188- if *compress {
189- _, _ = session.Stderr().Write([]byte("compression is currently unsupported\r\n"))
190- return
191- }
192-
193- if opts.PreserveUid {
194- _, _ = session.Stderr().Write([]byte("uid preservation will not work as we don't retain user information\r\n"))
195- return
196- }
197-
198- if opts.PreserveGid {
199- _, _ = session.Stderr().Write([]byte("gid preservation will not work as we don't retain user information\r\n"))
200- return
201- }
202-
203- if err := rsyncsender.ClientRun(opts, session, fileHandler, fileHandler.root, true); err != nil {
204- writeHandler.GetLogger().Error("error running rsync sender: ", err)
205- }
206- return
207- }
208- }
209-
210- opts, parser := rsyncreceiver.NewGetOpt()
211-
212- compress := parser.Bool("z", false)
213-
214- _, _ = parser.Parse(cmdFlags[1:])
215-
216- fileHandler.recursive = opts.Recurse
217- fileHandler.ignoreTimes = opts.IgnoreTimes
218-
219- if *compress {
220- _, _ = session.Stderr().Write([]byte("compression is currently unsupported\r\n"))
221- return
222- }
223-
224- if opts.PreserveUid {
225- _, _ = session.Stderr().Write([]byte("uid preservation will not work as we don't retain user information\r\n"))
226- return
227- }
228-
229- if opts.PreserveGid {
230- _, _ = session.Stderr().Write([]byte("gid preservation will not work as we don't retain user information\r\n"))
231- return
232- }
233-
234- if _, err := rsyncreceiver.ClientRun(opts, session, fileHandler, true); err != nil {
235- writeHandler.GetLogger().Error("error running rsync receiver: ", err)
236- }
237- }
238- }
239-}
+0,
-141
1@@ -1,141 +0,0 @@
2-package scp
3-
4-import (
5- "bufio"
6- "errors"
7- "fmt"
8- "io"
9- "io/fs"
10- "path/filepath"
11- "regexp"
12- "strconv"
13-
14- "github.com/charmbracelet/ssh"
15- "github.com/picosh/pico/wish/send/utils"
16-)
17-
18-var (
19- reTimestamp = regexp.MustCompile(`^T(\d{10}) 0 (\d{10}) 0$`)
20- reNewFolder = regexp.MustCompile(`^D(\d{4}) 0 (.*)$`)
21- reNewFile = regexp.MustCompile(`^C(\d{4}) (\d+) (.*)$`)
22-)
23-
24-type parseError struct {
25- subject string
26-}
27-
28-func (e parseError) Error() string {
29- return fmt.Sprintf("failed to parse: %q", e.subject)
30-}
31-
32-func copyFromClient(session ssh.Session, info Info, handler utils.CopyFromClientHandler) error {
33- // accepts the request
34- _, _ = session.Write(utils.NULL)
35-
36- writeErrors := []error{}
37- writeSuccess := []string{}
38-
39- var (
40- path = info.Path
41- r = bufio.NewReader(session)
42- mtime int64
43- atime int64
44- )
45-
46- for {
47- line, _, err := r.ReadLine()
48- if err != nil {
49- if errors.Is(err, io.EOF) {
50- break
51- }
52- return fmt.Errorf("failed to read line: %w", err)
53- }
54-
55- if matches := reTimestamp.FindAllStringSubmatch(string(line), 2); matches != nil {
56- mtime, err = strconv.ParseInt(matches[0][1], 10, 64)
57- if err != nil {
58- return parseError{string(line)}
59- }
60- atime, err = strconv.ParseInt(matches[0][2], 10, 64)
61- if err != nil {
62- return parseError{string(line)}
63- }
64-
65- // accepts the header
66- _, _ = session.Write(utils.NULL)
67- continue
68- }
69-
70- if matches := reNewFile.FindAllStringSubmatch(string(line), 3); matches != nil {
71- if len(matches) != 1 || len(matches[0]) != 4 {
72- return parseError{string(line)}
73- }
74-
75- mode, err := strconv.ParseUint(matches[0][1], 8, 32)
76- if err != nil {
77- return parseError{string(line)}
78- }
79-
80- size, err := strconv.ParseInt(matches[0][2], 10, 64)
81- if err != nil {
82- return parseError{string(line)}
83- }
84- name := matches[0][3]
85-
86- // accepts the header
87- _, _ = session.Write(utils.NULL)
88-
89- result, err := handler.Write(session, &utils.FileEntry{
90- Filepath: filepath.Join(path, name),
91- Mode: fs.FileMode(mode),
92- Size: size,
93- Mtime: mtime,
94- Atime: atime,
95- Reader: utils.NewLimitReader(r, int(size)),
96- })
97-
98- if err == nil {
99- writeSuccess = append(writeSuccess, result)
100- } else {
101- writeErrors = append(writeErrors, err)
102- fmt.Printf("failed to write file: %q: %v\n", name, err)
103- }
104-
105- // read the trailing nil char
106- _, _ = r.ReadByte() // TODO: check if it is indeed a utils.NULL?
107-
108- mtime = 0
109- atime = 0
110- // says 'hey im done'
111- _, _ = session.Write(utils.NULL)
112- continue
113- }
114-
115- if matches := reNewFolder.FindAllStringSubmatch(string(line), 2); matches != nil {
116- if len(matches) != 1 || len(matches[0]) != 3 {
117- return parseError{string(line)}
118- }
119-
120- name := matches[0][2]
121- path = filepath.Join(path, name)
122- // says 'hey im done'
123- _, _ = session.Write(utils.NULL)
124- continue
125- }
126-
127- if string(line) == "E" {
128- path = filepath.Dir(path)
129-
130- // says 'hey im done'
131- _, _ = session.Write(utils.NULL)
132- continue
133- }
134-
135- return fmt.Errorf("unhandled input: %q", string(line))
136- }
137-
138- utils.PrintMsg(session, writeSuccess, writeErrors)
139-
140- _, _ = session.Write(utils.NULL)
141- return nil
142-}
+0,
-12
1@@ -1,12 +0,0 @@
2-package scp
3-
4-import (
5- "errors"
6-
7- "github.com/charmbracelet/ssh"
8- "github.com/picosh/pico/wish/send/utils"
9-)
10-
11-func copyToClient(session ssh.Session, info Info, handler utils.CopyFromClientHandler) error {
12- return errors.New("unsupported, use rsync or sftp")
13-}
+0,
-103
1@@ -1,103 +0,0 @@
2-package scp
3-
4-import (
5- "fmt"
6-
7- "github.com/charmbracelet/ssh"
8- "github.com/charmbracelet/wish"
9- "github.com/picosh/pico/wish/send/utils"
10-)
11-
12-func Middleware(writeHandler utils.CopyFromClientHandler) wish.Middleware {
13- return func(sshHandler ssh.Handler) ssh.Handler {
14- return func(session ssh.Session) {
15- defer func() {
16- if r := recover(); r != nil {
17- writeHandler.GetLogger().Error("error running scp middleware: ", r)
18- _, _ = session.Stderr().Write([]byte("error running scp middleware, check the flags you are using\r\n"))
19- }
20- }()
21-
22- cmd := session.Command()
23- if len(cmd) == 0 || cmd[0] != "scp" {
24- sshHandler(session)
25- return
26- }
27-
28- info := GetInfo(cmd)
29- if !info.Ok {
30- sshHandler(session)
31- return
32- }
33-
34- var err error
35-
36- switch info.Op {
37- case OpCopyToClient:
38- if writeHandler == nil {
39- err = fmt.Errorf("no handler provided for scp -t")
40- break
41- }
42- err = copyToClient(session, info, writeHandler)
43- case OpCopyFromClient:
44- if writeHandler == nil {
45- err = fmt.Errorf("no handler provided for scp -t")
46- break
47- }
48- err = copyFromClient(session, info, writeHandler)
49- }
50- if err != nil {
51- utils.ErrorHandler(session, err)
52- }
53- }
54- }
55-}
56-
57-// Op defines which kind of SCP Operation is going on.
58-type Op byte
59-
60-const (
61- // OpCopyToClient is when a file is being copied from the server to the client.
62- OpCopyToClient Op = 'f'
63-
64- // OpCopyFromClient is when a file is being copied from the client into the server.
65- OpCopyFromClient Op = 't'
66-)
67-
68-// Info provides some information about the current SCP Operation.
69-type Info struct {
70- // Ok is true if the current session is a SCP.
71- Ok bool
72-
73- // Recursice is true if its a recursive SCP.
74- Recursive bool
75-
76- // Path is the server path of the scp operation.
77- Path string
78-
79- // Op is the SCP operation kind.
80- Op Op
81-}
82-
83-func GetInfo(cmd []string) Info {
84- info := Info{}
85- if len(cmd) == 0 || cmd[0] != "scp" {
86- return info
87- }
88-
89- for i, p := range cmd {
90- switch p {
91- case "-r":
92- info.Recursive = true
93- case "-f":
94- info.Op = OpCopyToClient
95- info.Path = cmd[i+1]
96- case "-t":
97- info.Op = OpCopyFromClient
98- info.Path = cmd[i+1]
99- }
100- }
101-
102- info.Ok = true
103- return info
104-}
+0,
-23
1@@ -1,23 +0,0 @@
2-package send
3-
4-import (
5- "github.com/charmbracelet/ssh"
6- "github.com/charmbracelet/wish"
7- "github.com/picosh/pico/wish/pipe"
8- "github.com/picosh/pico/wish/send/auth"
9- "github.com/picosh/pico/wish/send/rsync"
10- "github.com/picosh/pico/wish/send/scp"
11- "github.com/picosh/pico/wish/send/sftp"
12- "github.com/picosh/pico/wish/send/utils"
13-)
14-
15-func Middleware(writeHandler utils.CopyFromClientHandler) ssh.Option {
16- return func(server *ssh.Server) error {
17- err := wish.WithMiddleware(pipe.Middleware(writeHandler, ""), scp.Middleware(writeHandler), rsync.Middleware(writeHandler), auth.Middleware(writeHandler))(server)
18- if err != nil {
19- return err
20- }
21-
22- return sftp.SSHOption(writeHandler)(server)
23- }
24-}
+0,
-100
1@@ -1,100 +0,0 @@
2-package sftp
3-
4-import (
5- "bytes"
6- "errors"
7- "io"
8- "os"
9-
10- "slices"
11-
12- "github.com/charmbracelet/ssh"
13- "github.com/picosh/pico/wish/send/utils"
14- "github.com/pkg/sftp"
15-)
16-
17-type listerat []os.FileInfo
18-
19-func (f listerat) ListAt(ls []os.FileInfo, offset int64) (int, error) {
20- var n int
21- if offset >= int64(len(f)) {
22- return 0, io.EOF
23- }
24- n = copy(ls, f[offset:])
25- if n < len(ls) {
26- return n, io.EOF
27- }
28- return n, nil
29-}
30-
31-type handler struct {
32- session ssh.Session
33- writeHandler utils.CopyFromClientHandler
34-}
35-
36-func (f *handler) Filecmd(r *sftp.Request) error {
37- switch r.Method {
38- case "Remove":
39- entry := toFileEntry(r)
40- entry.Reader = bytes.NewReader(nil)
41-
42- _, err := f.writeHandler.Write(f.session, entry)
43-
44- return err
45- case "Setstat", "Mkdir":
46- return nil
47- }
48- return errors.New("unsupported")
49-}
50-
51-func (f *handler) Filelist(r *sftp.Request) (sftp.ListerAt, error) {
52- switch r.Method {
53- case "List", "Stat":
54- list := r.Method == "List"
55-
56- listData, err := f.writeHandler.List(f.session, r.Filepath, list, false)
57- if err != nil {
58- return nil, err
59- }
60-
61- if list {
62- listData = slices.DeleteFunc(listData, func(f os.FileInfo) bool {
63- return f.Name() == "/"
64- })
65- }
66-
67- return listerat(listData), nil
68- }
69-
70- return nil, errors.New("unsupported")
71-}
72-
73-func toFileEntry(r *sftp.Request) *utils.FileEntry {
74- entry := &utils.FileEntry{
75- Filepath: r.Filepath,
76- Mode: r.Attributes().FileMode(),
77- Size: int64(r.Attributes().Size),
78- Mtime: int64(r.Attributes().Mtime),
79- Atime: int64(r.Attributes().Atime),
80- }
81- return entry
82-}
83-
84-func (f *handler) Filewrite(r *sftp.Request) (io.WriterAt, error) {
85- entry := toFileEntry(r)
86- buf := &buffer{}
87- entry.Reader = buf
88-
89- return fakeWrite{fileEntry: entry, buf: buf, handler: f}, nil
90-}
91-
92-func (f *handler) Fileread(r *sftp.Request) (io.ReaderAt, error) {
93- if r.Filepath == "/" {
94- return nil, os.ErrInvalid
95- }
96-
97- fileEntry := toFileEntry(r)
98- _, reader, err := f.writeHandler.Read(f.session, fileEntry)
99-
100- return reader, err
101-}
+0,
-57
1@@ -1,57 +0,0 @@
2-package sftp
3-
4-import (
5- "errors"
6- "io"
7-
8- "github.com/charmbracelet/ssh"
9- "github.com/picosh/pico/wish/send/utils"
10- "github.com/pkg/sftp"
11-)
12-
13-func SSHOption(writeHandler utils.CopyFromClientHandler) ssh.Option {
14- return func(server *ssh.Server) error {
15- if server.SubsystemHandlers == nil {
16- server.SubsystemHandlers = map[string]ssh.SubsystemHandler{}
17- }
18-
19- server.SubsystemHandlers["sftp"] = SubsystemHandler(writeHandler)
20- return nil
21- }
22-}
23-
24-func SubsystemHandler(writeHandler utils.CopyFromClientHandler) ssh.SubsystemHandler {
25- return func(session ssh.Session) {
26- defer func() {
27- if r := recover(); r != nil {
28- writeHandler.GetLogger().Error("error running sftp middleware: ", r)
29- _, _ = session.Stderr().Write([]byte("error running sftp middleware, check the flags you are using\r\n"))
30- }
31- }()
32-
33- err := writeHandler.Validate(session)
34- if err != nil {
35- utils.ErrorHandler(session, err)
36- return
37- }
38-
39- handler := &handler{
40- session: session,
41- writeHandler: writeHandler,
42- }
43-
44- handlers := sftp.Handlers{
45- FilePut: handler,
46- FileList: handler,
47- FileGet: handler,
48- FileCmd: handler,
49- }
50-
51- requestServer := sftp.NewRequestServer(session, handlers)
52-
53- err = requestServer.Serve()
54- if err != nil && !errors.Is(err, io.EOF) {
55- writeHandler.GetLogger().Error("Error serving sftp subsystem: ", err)
56- }
57- }
58-}
+0,
-69
1@@ -1,69 +0,0 @@
2-package sftp
3-
4-import (
5- "fmt"
6- "io"
7- "sync"
8-
9- "github.com/picosh/pico/wish/send/utils"
10-)
11-
12-type buffer struct {
13- buf []byte
14- m sync.Mutex
15- off int
16-}
17-
18-func (b *buffer) WriteAt(p []byte, pos int64) (n int, err error) {
19- pLen := len(p)
20- expLen := pos + int64(pLen)
21- b.m.Lock()
22- defer b.m.Unlock()
23- if int64(len(b.buf)) < expLen {
24- if int64(cap(b.buf)) < expLen {
25- newBuf := make([]byte, expLen)
26- copy(newBuf, b.buf)
27- b.buf = newBuf
28- }
29- b.buf = b.buf[:expLen]
30- }
31- copy(b.buf[pos:], p)
32- return pLen, nil
33-}
34-
35-func (b *buffer) Read(p []byte) (n int, err error) {
36- b.m.Lock()
37- defer b.m.Unlock()
38- if len(b.buf) <= b.off {
39- if len(p) == 0 {
40- return 0, nil
41- }
42- return 0, io.EOF
43- }
44- n = copy(p, b.buf[b.off:])
45- b.off += n
46- return n, nil
47-}
48-
49-type fakeWrite struct {
50- fileEntry *utils.FileEntry
51- handler *handler
52- buf *buffer
53-}
54-
55-func (f fakeWrite) WriteAt(p []byte, off int64) (int, error) {
56- return f.buf.WriteAt(p, off)
57-}
58-
59-func (f fakeWrite) Close() error {
60- msg, err := f.handler.writeHandler.Write(f.handler.session, f.fileEntry)
61- if err != nil {
62- errMsg := fmt.Sprintf("%s\r\n", err.Error())
63- _, err = f.handler.session.Stderr().Write([]byte(errMsg))
64- }
65- if msg != "" {
66- nMsg := fmt.Sprintf("%s\r\n", msg)
67- _, err = f.handler.session.Stderr().Write([]byte(nMsg))
68- }
69- return err
70-}
+0,
-31
1@@ -1,31 +0,0 @@
2-package utils
3-
4-import (
5- "os"
6- "time"
7-)
8-
9-type VirtualFile struct {
10- FName string
11- FIsDir bool
12- FSize int64
13- FModTime time.Time
14- FSys any
15-}
16-
17-func (f *VirtualFile) Name() string { return f.FName }
18-func (f *VirtualFile) Size() int64 { return f.FSize }
19-func (f *VirtualFile) Mode() os.FileMode {
20- if f.FIsDir {
21- return os.FileMode(0755) | os.ModeDir
22- }
23- return os.FileMode(0644)
24-}
25-func (f *VirtualFile) ModTime() time.Time {
26- if f.FModTime.IsZero() {
27- return time.Now()
28- }
29- return f.FModTime
30-}
31-func (f *VirtualFile) IsDir() bool { return f.FIsDir }
32-func (f *VirtualFile) Sys() any { return f.FSys }
+0,
-35
1@@ -1,35 +0,0 @@
2-package utils
3-
4-import (
5- "io"
6- "sync"
7-)
8-
9-func NewLimitReader(r io.Reader, limit int) io.Reader {
10- return &LimitReader{
11- r: r,
12- left: limit,
13- }
14-}
15-
16-type LimitReader struct {
17- r io.Reader
18-
19- lock sync.Mutex
20- left int
21-}
22-
23-func (r *LimitReader) Read(b []byte) (int, error) {
24- r.lock.Lock()
25- defer r.lock.Unlock()
26-
27- if r.left <= 0 {
28- return 0, io.EOF
29- }
30- if len(b) > r.left {
31- b = b[0:r.left]
32- }
33- n, err := r.r.Read(b)
34- r.left -= n
35- return n, err
36-}
+0,
-44
1@@ -1,44 +0,0 @@
2-package utils
3-
4-import (
5- "bytes"
6- "io"
7- "testing"
8-
9- "github.com/matryer/is"
10-)
11-
12-func TestLimitedReader(t *testing.T) {
13- t.Run("partial", func(t *testing.T) {
14- is := is.New(t)
15- var b bytes.Buffer
16- b.WriteString("writing some bytes")
17- r := NewLimitReader(&b, 7)
18-
19- bts, err := io.ReadAll(r)
20- is.NoErr(err)
21- is.Equal("writing", string(bts))
22- })
23-
24- t.Run("full", func(t *testing.T) {
25- is := is.New(t)
26- var b bytes.Buffer
27- b.WriteString("some text")
28- r := NewLimitReader(&b, b.Len())
29-
30- bts, err := io.ReadAll(r)
31- is.NoErr(err)
32- is.Equal("some text", string(bts))
33- })
34-
35- t.Run("pass limit", func(t *testing.T) {
36- is := is.New(t)
37- var b bytes.Buffer
38- b.WriteString("another text")
39- r := NewLimitReader(&b, b.Len()+10)
40-
41- bts, err := io.ReadAll(r)
42- is.NoErr(err)
43- is.Equal("another text", string(bts))
44- })
45-}
+0,
-99
1@@ -1,99 +0,0 @@
2-package utils
3-
4-import (
5- "encoding/base64"
6- "fmt"
7- "io"
8- "io/fs"
9- "os"
10- "path/filepath"
11- "strconv"
12-
13- "github.com/charmbracelet/ssh"
14- "go.uber.org/zap"
15-)
16-
17-// NULL is an array with a single NULL byte.
18-var NULL = []byte{'\x00'}
19-
20-// FileEntry is an Entry that reads from a Reader, defining a file and
21-// its contents.
22-type FileEntry struct {
23- Filepath string
24- Mode fs.FileMode
25- Size int64
26- Reader io.Reader
27- Atime int64
28- Mtime int64
29-}
30-
31-// Write a file to the given writer.
32-func (e *FileEntry) Write(w io.Writer) error {
33- if e.Mtime > 0 && e.Atime > 0 {
34- if _, err := fmt.Fprintf(w, "T%d 0 %d 0\n", e.Mtime, e.Atime); err != nil {
35- return fmt.Errorf("failed to write file: %q: %w", e.Filepath, err)
36- }
37- }
38- fname := filepath.Base(e.Filepath)
39- if _, err := fmt.Fprintf(w, "C%s %d %s\n", octalPerms(e.Mode), e.Size, fname); err != nil {
40- return fmt.Errorf("failed to write file: %q: %w", e.Filepath, err)
41- }
42-
43- if _, err := io.Copy(w, e.Reader); err != nil {
44- return fmt.Errorf("failed to read file: %q: %w", e.Filepath, err)
45- }
46-
47- if _, err := w.Write(NULL); err != nil {
48- return fmt.Errorf("failed to write file: %q: %w", e.Filepath, err)
49- }
50- return nil
51-}
52-
53-func octalPerms(info fs.FileMode) string {
54- return "0" + strconv.FormatUint(uint64(info.Perm()), 8)
55-}
56-
57-// CopyFromClientHandler is a handler that can be implemented to handle files
58-// being copied from the client to the server.
59-type CopyFromClientHandler interface {
60- // Write should write the given file.
61- Write(ssh.Session, *FileEntry) (string, error)
62- Read(ssh.Session, *FileEntry) (os.FileInfo, ReaderAtCloser, error)
63- List(ssh ssh.Session, path string, isDir bool, recursive bool) ([]os.FileInfo, error)
64- GetLogger() *zap.SugaredLogger
65- Validate(ssh.Session) error
66-}
67-
68-func KeyText(session ssh.Session) (string, error) {
69- if session.PublicKey() == nil {
70- return "", fmt.Errorf("session doesn't have public key")
71- }
72- kb := base64.StdEncoding.EncodeToString(session.PublicKey().Marshal())
73- return fmt.Sprintf("%s %s", session.PublicKey().Type(), kb), nil
74-}
75-
76-func ErrorHandler(session ssh.Session, err error) {
77- _, _ = fmt.Fprint(session.Stderr(), err, "\r\n")
78- _ = session.Exit(1)
79- _ = session.Close()
80-}
81-
82-func PrintMsg(session ssh.Session, stdout []string, stderr []error) {
83- output := ""
84- if len(stdout) > 0 {
85- for _, msg := range stdout {
86- if msg != "" {
87- output += fmt.Sprintf("%s\r\n", msg)
88- }
89- }
90- _, _ = fmt.Fprintln(session.Stderr(), output)
91- }
92-
93- outputErr := ""
94- if len(stderr) > 0 {
95- for _, err := range stderr {
96- outputErr += fmt.Sprintf("%v\r\n", err)
97- }
98- _, _ = fmt.Fprintln(session.Stderr(), outputErr)
99- }
100-}