repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

commit
cd46782
parent
2a2904f
author
Eric Bower
date
2023-11-26 19:37:41 +0000 UTC
refactor: move `wish/*` to its own repo (#62)

39 files changed,  +68, -1275
M go.mod
M go.sum
M cmd/feeds/ssh/main.go
+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{}
M cmd/imgs/ssh/main.go
+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{}
M cmd/lists/ssh/main.go
+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{}
M cmd/prose/ssh/main.go
+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{}
M filehandlers/assets/asset.go
+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) {
M filehandlers/assets/handler.go
+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 }
M filehandlers/imgs/client.go
+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 {
M filehandlers/imgs/handler.go
+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 }
M filehandlers/imgs/img.go
+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) {
M filehandlers/post_handler.go
+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=
M imgs/client.go
+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 {
M pastes/cms.go
+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{}
M pgs/api.go
+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 
M pgs/ssh.go
+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{}
M pgs/wish.go
+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) {
M shared/bucket.go
+1, -1
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 {
R wish/send/utils/io.go => shared/reader.go
+4, -23
 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 
M shared/storage/fs.go
+1, -1
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
M shared/storage/minio.go
+1, -1
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 {
M shared/storage/storage.go
+1, -1
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 {
D wish/cmd/server/main.go
+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-}
D wish/list/list.go
+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-}
D wish/pipe/pipe.go
+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-}
D wish/proxy/middleware.go
+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-}
D wish/send/auth/auth.go
+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-}
D wish/send/rsync/rsync.go
+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-}
D wish/send/scp/copy_from_client.go
+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-}
D wish/send/scp/copy_to_client.go
+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-}
D wish/send/scp/scp.go
+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-}
D wish/send/send.go
+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-}
D wish/send/sftp/handler.go
+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-}
D wish/send/sftp/sftp.go
+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-}
D wish/send/sftp/writer.go
+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-}
D wish/send/utils/file.go
+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 }
D wish/send/utils/limit_reader.go
+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-}
D wish/send/utils/limit_reader_test.go
+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-}
D wish/send/utils/utils.go
+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-}