84 lines
2.1 KiB
Go
84 lines
2.1 KiB
Go
// Package transfer handles binary content (images, files) transfer between
|
|
// clip-sync peers. It is kept separate from the text clipboard path so that
|
|
// text sync remains simple and safe; binary content is sent explicitly and is
|
|
// written to disk on the receiving side only when enabled.
|
|
package transfer
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"mime"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
// File is the wire payload for a transferred binary object.
|
|
type File struct {
|
|
Name string `json:"name"`
|
|
Mime string `json:"mime"`
|
|
Data string `json:"data"` // base64-encoded content
|
|
Ts int64 `json:"ts"`
|
|
Origin string `json:"origin"`
|
|
}
|
|
|
|
// ReadFile reads a file from disk and encodes it as a transfer.File payload.
|
|
func ReadFile(path, origin string) (*File, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mimeType := mime.TypeByExtension(filepath.Ext(path))
|
|
if mimeType == "" {
|
|
mimeType = "application/octet-stream"
|
|
}
|
|
|
|
return &File{
|
|
Name: filepath.Base(path),
|
|
Mime: mimeType,
|
|
Data: base64.StdEncoding.EncodeToString(data),
|
|
Ts: time.Now().UnixNano(),
|
|
Origin: origin,
|
|
}, nil
|
|
}
|
|
|
|
// Decode returns the raw bytes of the file.
|
|
func (f *File) Decode() ([]byte, error) {
|
|
if f.Data == "" {
|
|
return nil, fmt.Errorf("transfer: empty data")
|
|
}
|
|
return base64.StdEncoding.DecodeString(f.Data)
|
|
}
|
|
|
|
// SaveTo writes the decoded content to the given directory, sanitizing the
|
|
// file name to avoid path traversal. It returns the written path.
|
|
func (f *File) SaveTo(dir string) (string, error) {
|
|
data, err := f.Decode()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
name := filepath.Base(f.Name)
|
|
if name == "." || name == "" || name == string(filepath.Separator) {
|
|
name = "received.bin"
|
|
}
|
|
|
|
// Avoid overwriting existing files: append a numeric suffix if needed.
|
|
dest := filepath.Join(dir, name)
|
|
for i := 1; ; i++ {
|
|
if _, err := os.Stat(dest); os.IsNotExist(err) {
|
|
break
|
|
}
|
|
ext := filepath.Ext(name)
|
|
base := name[:len(name)-len(ext)]
|
|
dest = filepath.Join(dir, fmt.Sprintf("%s (%d)%s", base, i, ext))
|
|
}
|
|
|
|
return dest, os.WriteFile(dest, data, 0644)
|
|
}
|