Antonio Mika
·
2026-10-10
1package utils
2
3import (
4 "encoding/base64"
5 "errors"
6 "fmt"
7 "io"
8 "io/fs"
9 "log/slog"
10 "os"
11 "path/filepath"
12 "strconv"
13
14 "github.com/picosh/pico/pkg/pssh"
15)
16
17// NULL is an array with a single NULL byte.
18var NULL = []byte{'\x00'}
19
20// ErrFileIgnored marks a file deliberately excluded by upload policy.
21var ErrFileIgnored = errors.New("file ignored")
22
23// FileEntry is an Entry that reads from a Reader, defining a file and
24// its contents.
25type FileEntry struct {
26 Filepath string
27 Mode fs.FileMode
28 Size int64
29 Reader io.Reader
30 Atime int64
31 Mtime int64
32 Metadata map[string]string
33}
34
35// Write a file to the given writer.
36func (e *FileEntry) Write(w io.Writer) error {
37 if e.Mtime > 0 && e.Atime > 0 {
38 if _, err := fmt.Fprintf(w, "T%d 0 %d 0\n", e.Mtime, e.Atime); err != nil {
39 return fmt.Errorf("failed to write file: %q: %w", e.Filepath, err)
40 }
41 }
42 fname := filepath.Base(e.Filepath)
43 if _, err := fmt.Fprintf(w, "C%s %d %s\n", octalPerms(e.Mode), e.Size, fname); err != nil {
44 return fmt.Errorf("failed to write file: %q: %w", e.Filepath, err)
45 }
46
47 if _, err := io.Copy(w, e.Reader); err != nil {
48 return fmt.Errorf("failed to read file: %q: %w", e.Filepath, err)
49 }
50
51 if _, err := w.Write(NULL); err != nil {
52 return fmt.Errorf("failed to write file: %q: %w", e.Filepath, err)
53 }
54 return nil
55}
56
57func octalPerms(info fs.FileMode) string {
58 return "0" + strconv.FormatUint(uint64(info.Perm()), 8)
59}
60
61// CopyFromClientHandler is a handler that can be implemented to handle files
62// being copied from the client to the server.
63type CopyFromClientHandler interface {
64 // Write should write the given file.
65 Delete(*pssh.SSHServerConnSession, *FileEntry) error
66 Write(*pssh.SSHServerConnSession, *FileEntry) (string, error)
67 Read(*pssh.SSHServerConnSession, *FileEntry) (os.FileInfo, ReadAndReaderAtCloser, error)
68 List(*pssh.SSHServerConnSession, string, bool, bool) ([]os.FileInfo, error)
69 GetLogger(*pssh.SSHServerConnSession) *slog.Logger
70 Validate(*pssh.SSHServerConnSession) error
71}
72
73// WriteValidator is implemented by handlers that can check a write before
74// its contents arrive, so SFTP can refuse a file when it is opened.
75type WriteValidator interface {
76 ValidateWrite(*pssh.SSHServerConnSession, *FileEntry) error
77}
78
79// FileRenamer is implemented by handlers that can move a stored file in
80// place, replacing any file at the destination. It returns
81// errors.ErrUnsupported for a move it can't make, and SFTP copies the file
82// instead.
83type FileRenamer interface {
84 Rename(s *pssh.SSHServerConnSession, from, to *FileEntry) error
85}
86
87// ImplicitDirs is implemented by handlers with directories that exist
88// before anything is stored in them. A stat of one reports a directory, so
89// a client copying a single file there puts the file inside it.
90type ImplicitDirs interface {
91 IsImplicitDir(s *pssh.SSHServerConnSession, path string) bool
92}
93
94func KeyText(session *pssh.SSHServerConnSession) (string, error) {
95 if session.PublicKey() == nil {
96 return "", fmt.Errorf("session doesn't have public key")
97 }
98 kb := base64.StdEncoding.EncodeToString(session.PublicKey().Marshal())
99 return fmt.Sprintf("%s %s", session.PublicKey().Type(), kb), nil
100}
101
102func ErrorHandler(session *pssh.SSHServerConnSession, err error) {
103 _, _ = fmt.Fprint(session.Stderr(), err, "\r\n")
104 _ = session.Exit(1)
105 _ = session.Close()
106}
107
108func PrintMsg(session *pssh.SSHServerConnSession, stdout []string, stderr []error) {
109 output := ""
110 if len(stdout) > 0 {
111 for _, msg := range stdout {
112 if msg != "" {
113 output += fmt.Sprintf("%s\r\n", msg)
114 }
115 }
116 _, _ = fmt.Fprintln(session.Stderr(), output)
117 }
118
119 outputErr := ""
120 if len(stderr) > 0 {
121 for _, err := range stderr {
122 outputErr += fmt.Sprintf("%v\r\n", err)
123 }
124 _, _ = fmt.Fprintln(session.Stderr(), outputErr)
125 }
126}