// 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) }