package filesharing import ( "crypto/rand" "cwtch.im/cwtch/model" "cwtch.im/cwtch/model/attr" "cwtch.im/cwtch/peer" "cwtch.im/cwtch/protocol/files" "encoding/hex" "encoding/json" "errors" "fmt" "git.openprivacy.ca/openprivacy/log" "io" "math" path "path/filepath" "strconv" ) // Functionality groups some common UI triggered functions for contacts... type Functionality struct { } // FunctionalityGate returns contact.Functionality always func FunctionalityGate(experimentMap map[string]bool) (*Functionality, error) { if experimentMap["filesharing"] == true { return new(Functionality), nil } return nil, errors.New("filesharing is not enabled") } // OverlayMessage presents the canonical format of the File Sharing functionality Overlay Message // This is the format that the UI will parse to display the message type OverlayMessage struct { Name string `json:"f"` Hash string `json:"h"` Nonce string `json:"n"` Size uint64 `json:"s"` } // DownloadFile given a profile, a conversation handle and a file sharing key, start off a download process // to downloadFilePath func (f *Functionality) DownloadFile(profile peer.CwtchPeer, handle string, downloadFilePath string, manifestFilePath string, key string) { // Store local.filesharing.filekey.manifest as the location of the manifest profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest", key), manifestFilePath) // Store local.filesharing.filekey as the location of the download profile.SetScopedZonedAttribute(attr.LocalScope, attr.FilesharingZone, key, downloadFilePath) // Get the value of conversation.filesharing.filekey.manifest.size from `handle` profile.SendScopedZonedGetValToContact(handle, attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest.size", key)) } // ShareFile given a profile and a conversation handle, sets up a file sharing process to share the file // at filepath func (f *Functionality) ShareFile(filepath string, profile peer.CwtchPeer, handle string) error { manifest, err := files.CreateManifest(filepath) if err != nil { return err } var nonce [24]byte if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { log.Errorf("Cannot read from random: %v\n", err) return err } message := OverlayMessage{ Name: path.Base(manifest.FileName), Hash: hex.EncodeToString(manifest.RootHash), Nonce: hex.EncodeToString(nonce[:]), Size: manifest.FileSizeInBytes, } data, _ := json.Marshal(message) wrapper := model.MessageWrapper{ Overlay: model.OverlayFileSharing, Data: string(data), } wrapperJSON, _ := json.Marshal(wrapper) key := fmt.Sprintf("%x.%x", manifest.RootHash, nonce) serializedManifest, _ := json.Marshal(manifest) // Store the size of the manifest (in chunks) as part of the public scope so contacts who we share the file with // can fetch the manifest as if it were a file. // manifest.FileName gets redacted in filesharing_subsystem (to remove the system-specific file hierarchy), // but we need to *store* the full path because the sender also uses it to locate the file lenDiff := len(filepath) - len(path.Base(filepath)) profile.SetScopedZonedAttribute(attr.ConversationScope, attr.FilesharingZone, fmt.Sprintf("%s.manifest.size", key), strconv.Itoa(int(math.Ceil(float64(len(serializedManifest)-lenDiff)/float64(files.DefaultChunkSize))))) profile.ShareFile(key, string(serializedManifest)) profile.SendMessage(handle, string(wrapperJSON)) return nil }