core: local encryption for auto-received inline files (e.g. small voice messages) (#3224)
* core: local encryption for auto-received inline files * update view, test
This commit is contained in:
parent
a35dc263b7
commit
c2a320640b
@ -257,6 +257,12 @@ func setXFTPConfig(_ cfg: XFTPFileConfig?) throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetEncryptLocalFiles(_ enable: Boolean) throws {
|
||||
let r = chatSendCmdSync(.apiSetEncryptLocalFiles(enable: enable))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiExportArchive(config: ArchiveConfig) async throws {
|
||||
try await sendCommandOkResp(.apiExportArchive(config: config))
|
||||
}
|
||||
@ -1152,6 +1158,7 @@ func initializeChat(start: Bool, dbKey: String? = nil, refreshInvitations: Bool
|
||||
try apiSetTempFolder(tempFolder: getTempFilesDirectory().path)
|
||||
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
|
||||
try setXFTPConfig(getXFTPCfg())
|
||||
// try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get())
|
||||
m.chatInitialized = true
|
||||
m.currentUser = try apiGetActiveUser()
|
||||
if m.currentUser == nil {
|
||||
|
@ -216,6 +216,7 @@ func startChat() -> DBMigrationResult? {
|
||||
try apiSetTempFolder(tempFolder: getTempFilesDirectory().path)
|
||||
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
|
||||
try setXFTPConfig(xftpConfig)
|
||||
// try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get())
|
||||
let justStarted = try apiStartChat()
|
||||
chatStarted = true
|
||||
if justStarted {
|
||||
@ -351,6 +352,12 @@ func setXFTPConfig(_ cfg: XFTPFileConfig?) throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetEncryptLocalFiles(_ enable: Boolean) throws {
|
||||
let r = chatSendCmdSync(.apiSetEncryptLocalFiles(enable: enable))
|
||||
if case .cmdOk = r { return }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? {
|
||||
guard apiGetActiveUser() != nil else {
|
||||
logger.debug("no active user")
|
||||
|
@ -32,6 +32,7 @@ public enum ChatCommand {
|
||||
case setTempFolder(tempFolder: String)
|
||||
case setFilesFolder(filesFolder: String)
|
||||
case apiSetXFTPConfig(config: XFTPFileConfig?)
|
||||
case apiSetEncryptLocalFiles(enable: Bool)
|
||||
case apiExportArchive(config: ArchiveConfig)
|
||||
case apiImportArchive(config: ArchiveConfig)
|
||||
case apiDeleteStorage
|
||||
@ -114,8 +115,8 @@ public enum ChatCommand {
|
||||
case apiGetNetworkStatuses
|
||||
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
|
||||
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
|
||||
case receiveFile(fileId: Int64, encrypted: Bool, inline: Bool?)
|
||||
case setFileToReceive(fileId: Int64, encrypted: Bool)
|
||||
case receiveFile(fileId: Int64, encrypted: Bool?, inline: Bool?)
|
||||
case setFileToReceive(fileId: Int64, encrypted: Bool?)
|
||||
case cancelFile(fileId: Int64)
|
||||
case showVersion
|
||||
case string(String)
|
||||
@ -152,6 +153,7 @@ public enum ChatCommand {
|
||||
} else {
|
||||
return "/_xftp off"
|
||||
}
|
||||
case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))"
|
||||
case let .apiExportArchive(cfg): return "/_db export \(encodeJSON(cfg))"
|
||||
case let .apiImportArchive(cfg): return "/_db import \(encodeJSON(cfg))"
|
||||
case .apiDeleteStorage: return "/_db delete"
|
||||
@ -247,13 +249,8 @@ public enum ChatCommand {
|
||||
case .apiGetNetworkStatuses: return "/_network_statuses"
|
||||
case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)"
|
||||
case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))"
|
||||
case let .receiveFile(fileId, encrypted, inline):
|
||||
let s = "/freceive \(fileId) encrypt=\(onOff(encrypted))"
|
||||
if let inline = inline {
|
||||
return s + " inline=\(onOff(inline))"
|
||||
}
|
||||
return s
|
||||
case let .setFileToReceive(fileId, encrypted): return "/_set_file_to_receive \(fileId) encrypt=\(onOff(encrypted))"
|
||||
case let .receiveFile(fileId, encrypt, inline): return "/freceive \(fileId)\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))"
|
||||
case let .setFileToReceive(fileId, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("encrypt", encrypt))"
|
||||
case let .cancelFile(fileId): return "/fcancel \(fileId)"
|
||||
case .showVersion: return "/version"
|
||||
case let .string(str): return str
|
||||
@ -283,6 +280,7 @@ public enum ChatCommand {
|
||||
case .setTempFolder: return "setTempFolder"
|
||||
case .setFilesFolder: return "setFilesFolder"
|
||||
case .apiSetXFTPConfig: return "apiSetXFTPConfig"
|
||||
case .apiSetEncryptLocalFiles: return "apiSetEncryptLocalFiles"
|
||||
case .apiExportArchive: return "apiExportArchive"
|
||||
case .apiImportArchive: return "apiImportArchive"
|
||||
case .apiDeleteStorage: return "apiDeleteStorage"
|
||||
@ -420,6 +418,13 @@ public enum ChatCommand {
|
||||
b ? "on" : "off"
|
||||
}
|
||||
|
||||
private func onOffParam(_ param: String, _ b: Bool?) -> String {
|
||||
if let b = b {
|
||||
return " \(param)=\(onOff(b))"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
private func maybePwd(_ pwd: String?) -> String {
|
||||
pwd == "" || pwd == nil ? "" : " " + encodeJSON(pwd)
|
||||
}
|
||||
|
@ -338,6 +338,7 @@ object ChatController {
|
||||
apiSetTempFolder(coreTmpDir.absolutePath)
|
||||
apiSetFilesFolder(appFilesDir.absolutePath)
|
||||
apiSetXFTPConfig(getXFTPCfg())
|
||||
// apiSetEncryptLocalFiles(appPrefs.privacyEncryptLocalFiles.get())
|
||||
val justStarted = apiStartChat()
|
||||
val users = listUsers()
|
||||
chatModel.users.clear()
|
||||
@ -559,6 +560,8 @@ object ChatController {
|
||||
throw Error("apiSetXFTPConfig bad response: ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
suspend fun apiSetEncryptLocalFiles(enable: Boolean) = sendCommandOkResp(CC.ApiSetEncryptLocalFiles(enable))
|
||||
|
||||
suspend fun apiExportArchive(config: ArchiveConfig) {
|
||||
val r = sendCmd(CC.ApiExportArchive(config))
|
||||
if (r is CR.CmdOk) return
|
||||
@ -1327,6 +1330,13 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendCommandOkResp(cmd: CC): Boolean {
|
||||
val r = sendCmd(cmd)
|
||||
val ok = r is CR.CmdOk
|
||||
if (!ok) apiErrorAlert(cmd.cmdType, generalGetString(MR.strings.error), r)
|
||||
return ok
|
||||
}
|
||||
|
||||
suspend fun apiGetVersion(): CoreVersionInfo? {
|
||||
val r = sendCmd(CC.ShowVersion())
|
||||
return if (r is CR.VersionInfo) {
|
||||
@ -1858,6 +1868,7 @@ sealed class CC {
|
||||
class SetTempFolder(val tempFolder: String): CC()
|
||||
class SetFilesFolder(val filesFolder: String): CC()
|
||||
class ApiSetXFTPConfig(val config: XFTPFileConfig?): CC()
|
||||
class ApiSetEncryptLocalFiles(val enable: Boolean): CC()
|
||||
class ApiExportArchive(val config: ArchiveConfig): CC()
|
||||
class ApiImportArchive(val config: ArchiveConfig): CC()
|
||||
class ApiDeleteStorage: CC()
|
||||
@ -1931,7 +1942,7 @@ sealed class CC {
|
||||
class ApiRejectContact(val contactReqId: Long): CC()
|
||||
class ApiChatRead(val type: ChatType, val id: Long, val range: ItemRange): CC()
|
||||
class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC()
|
||||
class ReceiveFile(val fileId: Long, val encrypted: Boolean, val inline: Boolean?): CC()
|
||||
class ReceiveFile(val fileId: Long, val encrypt: Boolean?, val inline: Boolean?): CC()
|
||||
class CancelFile(val fileId: Long): CC()
|
||||
class ShowVersion(): CC()
|
||||
|
||||
@ -1963,6 +1974,7 @@ sealed class CC {
|
||||
is SetTempFolder -> "/_temp_folder $tempFolder"
|
||||
is SetFilesFolder -> "/_files_folder $filesFolder"
|
||||
is ApiSetXFTPConfig -> if (config != null) "/_xftp on ${json.encodeToString(config)}" else "/_xftp off"
|
||||
is ApiSetEncryptLocalFiles -> "/_files_encrypt ${onOff(enable)}"
|
||||
is ApiExportArchive -> "/_db export ${json.encodeToString(config)}"
|
||||
is ApiImportArchive -> "/_db import ${json.encodeToString(config)}"
|
||||
is ApiDeleteStorage -> "/_db delete"
|
||||
@ -2039,7 +2051,10 @@ sealed class CC {
|
||||
is ApiGetNetworkStatuses -> "/_network_statuses"
|
||||
is ApiChatRead -> "/_read chat ${chatRef(type, id)} from=${range.from} to=${range.to}"
|
||||
is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}"
|
||||
is ReceiveFile -> "/freceive $fileId encrypt=${onOff(encrypted)}" + (if (inline == null) "" else " inline=${onOff(inline)}")
|
||||
is ReceiveFile ->
|
||||
"/freceive $fileId" +
|
||||
(if (encrypt == null) "" else " encrypt=${onOff(encrypt)}") +
|
||||
(if (inline == null) "" else " inline=${onOff(inline)}")
|
||||
is CancelFile -> "/fcancel $fileId"
|
||||
is ShowVersion -> "/version"
|
||||
}
|
||||
@ -2063,6 +2078,7 @@ sealed class CC {
|
||||
is SetTempFolder -> "setTempFolder"
|
||||
is SetFilesFolder -> "setFilesFolder"
|
||||
is ApiSetXFTPConfig -> "apiSetXFTPConfig"
|
||||
is ApiSetEncryptLocalFiles -> "apiSetEncryptLocalFiles"
|
||||
is ApiExportArchive -> "apiExportArchive"
|
||||
is ApiImportArchive -> "apiImportArchive"
|
||||
is ApiDeleteStorage -> "apiDeleteStorage"
|
||||
|
@ -209,6 +209,7 @@ newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agen
|
||||
cleanupManagerAsync <- newTVarIO Nothing
|
||||
timedItemThreads <- atomically TM.empty
|
||||
showLiveItems <- newTVarIO False
|
||||
encryptLocalFiles <- newTVarIO False
|
||||
userXFTPFileConfig <- newTVarIO $ xftpFileConfig cfg
|
||||
tempDirectory <- newTVarIO tempDir
|
||||
contactMergeEnabled <- newTVarIO True
|
||||
@ -236,6 +237,7 @@ newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agen
|
||||
cleanupManagerAsync,
|
||||
timedItemThreads,
|
||||
showLiveItems,
|
||||
encryptLocalFiles,
|
||||
userXFTPFileConfig,
|
||||
tempDirectory,
|
||||
logFilePath = logFile,
|
||||
@ -515,6 +517,7 @@ processChatCommand = \case
|
||||
APISetXFTPConfig cfg -> do
|
||||
asks userXFTPFileConfig >>= atomically . (`writeTVar` cfg)
|
||||
ok_
|
||||
APISetEncryptLocalFiles on -> chatWriteVar encryptLocalFiles on >> ok_
|
||||
SetContactMergeEnabled onOff -> do
|
||||
asks contactMergeEnabled >>= atomically . (`writeTVar` onOff)
|
||||
ok_
|
||||
@ -1773,19 +1776,16 @@ processChatCommand = \case
|
||||
ForwardFile chatName fileId -> forwardFile chatName fileId SendFile
|
||||
ForwardImage chatName fileId -> forwardFile chatName fileId SendImage
|
||||
SendFileDescription _chatName _f -> pure $ chatCmdError Nothing "TODO"
|
||||
ReceiveFile fileId encrypted rcvInline_ filePath_ -> withUser $ \_ ->
|
||||
ReceiveFile fileId encrypted_ rcvInline_ filePath_ -> withUser $ \_ ->
|
||||
withChatLock "receiveFile" . procCmd $ do
|
||||
(user, ft) <- withStore (`getRcvFileTransferById` fileId)
|
||||
ft' <- if encrypted then encryptLocalFile ft else pure ft
|
||||
encrypt <- (`fromMaybe` encrypted_) <$> chatReadVar encryptLocalFiles
|
||||
ft' <- (if encrypt then setFileToEncrypt else pure) ft
|
||||
receiveFile' user ft' rcvInline_ filePath_
|
||||
where
|
||||
encryptLocalFile ft = do
|
||||
cfArgs <- liftIO $ CF.randomArgs
|
||||
withStore' $ \db -> setFileCryptoArgs db fileId cfArgs
|
||||
pure (ft :: RcvFileTransfer) {cryptoArgs = Just cfArgs}
|
||||
SetFileToReceive fileId encrypted -> withUser $ \_ -> do
|
||||
SetFileToReceive fileId encrypted_ -> withUser $ \_ -> do
|
||||
withChatLock "setFileToReceive" . procCmd $ do
|
||||
cfArgs <- if encrypted then Just <$> liftIO CF.randomArgs else pure Nothing
|
||||
encrypt <- (`fromMaybe` encrypted_) <$> chatReadVar encryptLocalFiles
|
||||
cfArgs <- if encrypt then Just <$> liftIO CF.randomArgs else pure Nothing
|
||||
withStore' $ \db -> setRcvFileToReceive db fileId cfArgs
|
||||
ok_
|
||||
CancelFile fileId -> withUser $ \user@User {userId} ->
|
||||
@ -2410,6 +2410,12 @@ toFSFilePath :: ChatMonad' m => FilePath -> m FilePath
|
||||
toFSFilePath f =
|
||||
maybe f (</> f) <$> (readTVarIO =<< asks filesFolder)
|
||||
|
||||
setFileToEncrypt :: ChatMonad m => RcvFileTransfer -> m RcvFileTransfer
|
||||
setFileToEncrypt ft@RcvFileTransfer {fileId} = do
|
||||
cfArgs <- liftIO CF.randomArgs
|
||||
withStore' $ \db -> setFileCryptoArgs db fileId cfArgs
|
||||
pure (ft :: RcvFileTransfer) {cryptoArgs = Just cfArgs}
|
||||
|
||||
receiveFile' :: ChatMonad m => User -> RcvFileTransfer -> Maybe Bool -> Maybe FilePath -> m ChatResponse
|
||||
receiveFile' user ft rcvInline_ filePath_ = do
|
||||
(CRRcvFileAccepted user <$> acceptFileReceive user ft rcvInline_ filePath_) `catchChatError` processError
|
||||
@ -3931,14 +3937,17 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
||||
inline <- receiveInlineMode fInv (Just mc) fileChunkSize
|
||||
ft@RcvFileTransfer {fileId, xftpRcvFile} <- withStore $ \db -> createRcvFT db fInv inline fileChunkSize
|
||||
let fileProtocol = if isJust xftpRcvFile then FPXFTP else FPSMP
|
||||
(filePath, fileStatus) <- case inline of
|
||||
(filePath, fileStatus, ft') <- case inline of
|
||||
Just IFMSent -> do
|
||||
encrypt <- chatReadVar encryptLocalFiles
|
||||
ft' <- (if encrypt then setFileToEncrypt else pure) ft
|
||||
fPath <- getRcvFilePath fileId Nothing fileName True
|
||||
withStore' $ \db -> startRcvInlineFT db user ft fPath inline
|
||||
pure (Just fPath, CIFSRcvAccepted)
|
||||
_ -> pure (Nothing, CIFSRcvInvitation)
|
||||
let fileSource = CF.plain <$> filePath
|
||||
pure (ft, CIFile {fileId, fileName, fileSize, fileSource, fileStatus, fileProtocol})
|
||||
withStore' $ \db -> startRcvInlineFT db user ft' fPath inline
|
||||
pure (Just fPath, CIFSRcvAccepted, ft')
|
||||
_ -> pure (Nothing, CIFSRcvInvitation, ft)
|
||||
let RcvFileTransfer {cryptoArgs} = ft'
|
||||
fileSource = (`CryptoFile` cryptoArgs) <$> filePath
|
||||
pure (ft', CIFile {fileId, fileName, fileSize, fileSource, fileStatus, fileProtocol})
|
||||
|
||||
messageUpdate :: Contact -> SharedMsgId -> MsgContent -> RcvMessage -> MsgMeta -> Maybe Int -> Maybe Bool -> m ()
|
||||
messageUpdate ct@Contact {contactId} sharedMsgId mc msg@RcvMessage {msgId} msgMeta ttl live_ = do
|
||||
@ -5567,6 +5576,7 @@ chatCommandP =
|
||||
("/_files_folder " <|> "/files_folder ") *> (SetFilesFolder <$> filePath),
|
||||
"/_xftp " *> (APISetXFTPConfig <$> ("on " *> (Just <$> jsonP) <|> ("off" $> Nothing))),
|
||||
"/xftp " *> (APISetXFTPConfig <$> ("on" *> (Just <$> xftpCfgP) <|> ("off" $> Nothing))),
|
||||
"/_files_encrypt " *> (APISetEncryptLocalFiles <$> onOffP),
|
||||
"/contact_merge " *> (SetContactMergeEnabled <$> onOffP),
|
||||
"/_db export " *> (APIExportArchive <$> jsonP),
|
||||
"/db export" $> ExportArchive,
|
||||
@ -5743,8 +5753,8 @@ chatCommandP =
|
||||
("/fforward " <|> "/ff ") *> (ForwardFile <$> chatNameP' <* A.space <*> A.decimal),
|
||||
("/image_forward " <|> "/imgf ") *> (ForwardImage <$> chatNameP' <* A.space <*> A.decimal),
|
||||
("/fdescription " <|> "/fd") *> (SendFileDescription <$> chatNameP' <* A.space <*> filePath),
|
||||
("/freceive " <|> "/fr ") *> (ReceiveFile <$> A.decimal <*> (" encrypt=" *> onOffP <|> pure False) <*> optional (" inline=" *> onOffP) <*> optional (A.space *> filePath)),
|
||||
"/_set_file_to_receive " *> (SetFileToReceive <$> A.decimal <*> (" encrypt=" *> onOffP <|> pure False)),
|
||||
("/freceive " <|> "/fr ") *> (ReceiveFile <$> A.decimal <*> optional (" encrypt=" *> onOffP) <*> optional (" inline=" *> onOffP) <*> optional (A.space *> filePath)),
|
||||
"/_set_file_to_receive " *> (SetFileToReceive <$> A.decimal <*> optional (" encrypt=" *> onOffP)),
|
||||
("/fcancel " <|> "/fc ") *> (CancelFile <$> A.decimal),
|
||||
("/fstatus " <|> "/fs ") *> (FileStatus <$> A.decimal),
|
||||
"/simplex" *> (ConnectSimplex <$> incognitoP),
|
||||
|
@ -179,6 +179,7 @@ data ChatController = ChatController
|
||||
cleanupManagerAsync :: TVar (Maybe (Async ())),
|
||||
timedItemThreads :: TMap (ChatRef, ChatItemId) (TVar (Maybe (Weak ThreadId))),
|
||||
showLiveItems :: TVar Bool,
|
||||
encryptLocalFiles :: TVar Bool,
|
||||
userXFTPFileConfig :: TVar (Maybe XFTPFileConfig),
|
||||
tempDirectory :: TVar (Maybe FilePath),
|
||||
logFilePath :: Maybe FilePath,
|
||||
@ -221,6 +222,7 @@ data ChatCommand
|
||||
| SetTempFolder FilePath
|
||||
| SetFilesFolder FilePath
|
||||
| APISetXFTPConfig (Maybe XFTPFileConfig)
|
||||
| APISetEncryptLocalFiles Bool
|
||||
| SetContactMergeEnabled Bool
|
||||
| APIExportArchive ArchiveConfig
|
||||
| ExportArchive
|
||||
@ -393,8 +395,8 @@ data ChatCommand
|
||||
| ForwardFile ChatName FileTransferId
|
||||
| ForwardImage ChatName FileTransferId
|
||||
| SendFileDescription ChatName FilePath
|
||||
| ReceiveFile {fileId :: FileTransferId, storeEncrypted :: Bool, fileInline :: Maybe Bool, filePath :: Maybe FilePath}
|
||||
| SetFileToReceive {fileId :: FileTransferId, storeEncrypted :: Bool}
|
||||
| ReceiveFile {fileId :: FileTransferId, storeEncrypted :: Maybe Bool, fileInline :: Maybe Bool, filePath :: Maybe FilePath}
|
||||
| SetFileToReceive {fileId :: FileTransferId, storeEncrypted :: Maybe Bool}
|
||||
| CancelFile FileTransferId
|
||||
| FileStatus FileTransferId
|
||||
| ShowProfile -- UserId (not used in UI)
|
||||
|
@ -166,7 +166,7 @@ responseToView user_ ChatConfig {logLevel, showReactions, showReceipts, testView
|
||||
CRRcvFileDescrReady _ _ -> []
|
||||
CRRcvFileDescrNotReady _ _ -> []
|
||||
CRRcvFileProgressXFTP {} -> []
|
||||
CRRcvFileAccepted u ci -> ttyUser u $ savingFile' testView ci
|
||||
CRRcvFileAccepted u ci -> ttyUser u $ savingFile' ci
|
||||
CRRcvFileAcceptedSndCancelled u ft -> ttyUser u $ viewRcvFileSndCancelled ft
|
||||
CRSndFileCancelled u _ ftm fts -> ttyUser u $ viewSndFileCancelled ftm fts
|
||||
CRRcvFileCancelled u _ ft -> ttyUser u $ receivingFile_ "cancelled" ft
|
||||
@ -178,10 +178,10 @@ responseToView user_ ChatConfig {logLevel, showReactions, showReceipts, testView
|
||||
CRContactUpdated {user = u, fromContact = c, toContact = c'} -> ttyUser u $ viewContactUpdated c c' <> viewContactPrefsUpdated u c c'
|
||||
CRContactsMerged u intoCt mergedCt ct' -> ttyUser u $ viewContactsMerged intoCt mergedCt ct'
|
||||
CRReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} -> ttyUser u $ viewReceivedContactRequest c profile
|
||||
CRRcvFileStart u ci -> ttyUser u $ receivingFile_' "started" ci
|
||||
CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' "completed" ci
|
||||
CRRcvFileStart u ci -> ttyUser u $ receivingFile_' testView "started" ci
|
||||
CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' testView "completed" ci
|
||||
CRRcvFileSndCancelled u _ ft -> ttyUser u $ viewRcvFileSndCancelled ft
|
||||
CRRcvFileError u ci e -> ttyUser u $ receivingFile_' "error" ci <> [sShow e]
|
||||
CRRcvFileError u ci e -> ttyUser u $ receivingFile_' testView "error" ci <> [sShow e]
|
||||
CRSndFileStart u _ ft -> ttyUser u $ sendingFile_ "started" ft
|
||||
CRSndFileComplete u _ ft -> ttyUser u $ sendingFile_ "completed" ft
|
||||
CRSndFileStartXFTP {} -> []
|
||||
@ -1449,27 +1449,28 @@ humanReadableSize size
|
||||
mB = kB * 1024
|
||||
gB = mB * 1024
|
||||
|
||||
savingFile' :: Bool -> AChatItem -> [StyledString]
|
||||
savingFile' testView (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileSource = Just (CryptoFile filePath cfArgs_)}, chatDir}) =
|
||||
let from = case (chat, chatDir) of
|
||||
(DirectChat Contact {localDisplayName = c}, CIDirectRcv) -> " from " <> ttyContact c
|
||||
(_, CIGroupRcv GroupMember {localDisplayName = m}) -> " from " <> ttyContact m
|
||||
_ -> ""
|
||||
in ["saving file " <> sShow fileId <> from <> " to " <> plain filePath] <> cfArgsStr
|
||||
where
|
||||
cfArgsStr = case cfArgs_ of
|
||||
Just cfArgs@(CFArgs key nonce)
|
||||
| testView -> [plain $ LB.unpack $ J.encode cfArgs]
|
||||
| otherwise -> [plain $ "encryption key: " <> strEncode key <> ", nonce: " <> strEncode nonce]
|
||||
_ -> []
|
||||
savingFile' _ _ = ["saving file"] -- shouldn't happen
|
||||
savingFile' :: AChatItem -> [StyledString]
|
||||
savingFile' (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileSource = Just (CryptoFile filePath _)}, chatDir}) =
|
||||
["saving file " <> sShow fileId <> fileFrom chat chatDir <> " to " <> plain filePath]
|
||||
savingFile' _ = ["saving file"] -- shouldn't happen
|
||||
|
||||
receivingFile_' :: StyledString -> AChatItem -> [StyledString]
|
||||
receivingFile_' status (AChatItem _ _ (DirectChat c) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectRcv}) =
|
||||
[status <> " receiving " <> fileTransferStr fileId fileName <> " from " <> ttyContact' c]
|
||||
receivingFile_' status (AChatItem _ _ _ ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupRcv m}) =
|
||||
[status <> " receiving " <> fileTransferStr fileId fileName <> " from " <> ttyMember m]
|
||||
receivingFile_' status _ = [status <> " receiving file"] -- shouldn't happen
|
||||
receivingFile_' :: Bool -> String -> AChatItem -> [StyledString]
|
||||
receivingFile_' testView status (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileName, fileSource = Just (CryptoFile _ cfArgs_)}, chatDir}) =
|
||||
[plain status <> " receiving " <> fileTransferStr fileId fileName <> fileFrom chat chatDir] <> cfArgsStr cfArgs_
|
||||
where
|
||||
cfArgsStr (Just cfArgs@(CFArgs key nonce)) = [plain s | status == "completed"]
|
||||
where
|
||||
s =
|
||||
if testView
|
||||
then LB.toStrict $ J.encode cfArgs
|
||||
else "encryption key: " <> strEncode key <> ", nonce: " <> strEncode nonce
|
||||
cfArgsStr _ = []
|
||||
receivingFile_' _ status _ = [plain status <> " receiving file"] -- shouldn't happen
|
||||
|
||||
fileFrom :: ChatInfo c -> CIDirection c d -> StyledString
|
||||
fileFrom (DirectChat ct) CIDirectRcv = " from " <> ttyContact' ct
|
||||
fileFrom _ (CIGroupRcv m) = " from " <> ttyMember m
|
||||
fileFrom _ _ = ""
|
||||
|
||||
receivingFile_ :: StyledString -> RcvFileTransfer -> [StyledString]
|
||||
receivingFile_ status ft@RcvFileTransfer {senderDisplayName = c} =
|
||||
|
@ -33,6 +33,7 @@ chatFileTests = do
|
||||
describe "send and receive file" $ fileTestMatrix2 runTestFileTransfer
|
||||
describe "send file, receive and locally encrypt file" $ fileTestMatrix2 runTestFileTransferEncrypted
|
||||
it "send and receive file inline (without accepting)" testInlineFileTransfer
|
||||
it "send inline file, receive (without accepting) and locally encrypt" testInlineFileTransferEncrypted
|
||||
xit'' "accept inline file transfer, sender cancels during transfer" testAcceptInlineFileSndCancelDuringTransfer
|
||||
it "send and receive small file inline (default config)" testSmallInlineFileTransfer
|
||||
it "small file sent without acceptance is ignored in terminal by default" testSmallInlineFileIgnored
|
||||
@ -107,7 +108,6 @@ runTestFileTransferEncrypted alice bob = do
|
||||
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
||||
bob ##> "/fr 1 encrypt=on ./tests/tmp"
|
||||
bob <## "saving file 1 from alice to ./tests/tmp/test.pdf"
|
||||
Just (CFArgs key nonce) <- J.decode . LB.pack <$> getTermLine bob
|
||||
concurrently_
|
||||
(bob <## "started receiving file 1 (test.pdf) from alice")
|
||||
(alice <## "started sending file 1 (test.pdf) to bob")
|
||||
@ -123,6 +123,7 @@ runTestFileTransferEncrypted alice bob = do
|
||||
"completed sending file 1 (test.pdf) to bob"
|
||||
]
|
||||
]
|
||||
Just (CFArgs key nonce) <- J.decode . LB.pack <$> getTermLine bob
|
||||
src <- B.readFile "./tests/fixtures/test.pdf"
|
||||
-- dest <- B.readFile "./tests/tmp/test.pdf"
|
||||
-- dest `shouldBe` src
|
||||
@ -154,6 +155,34 @@ testInlineFileTransfer =
|
||||
where
|
||||
cfg = testCfg {inlineFiles = defaultInlineFilesConfig {offerChunks = 100, sendChunks = 100, receiveChunks = 100}}
|
||||
|
||||
testInlineFileTransferEncrypted :: HasCallStack => FilePath -> IO ()
|
||||
testInlineFileTransferEncrypted =
|
||||
testChatCfg2 cfg aliceProfile bobProfile $ \alice bob -> do
|
||||
connectUsers alice bob
|
||||
bob ##> "/_files_folder ./tests/tmp/"
|
||||
bob <## "ok"
|
||||
bob ##> "/_files_encrypt on"
|
||||
bob <## "ok"
|
||||
alice ##> "/_send @2 json {\"msgContent\":{\"type\":\"voice\", \"duration\":10, \"text\":\"\"}, \"filePath\":\"./tests/fixtures/test.jpg\"}"
|
||||
alice <# "@bob voice message (00:10)"
|
||||
alice <# "/f @bob ./tests/fixtures/test.jpg"
|
||||
-- below is not shown in "sent" mode
|
||||
-- alice <## "use /fc 1 to cancel sending"
|
||||
bob <# "alice> voice message (00:10)"
|
||||
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
|
||||
-- below is not shown in "sent" mode
|
||||
-- bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
||||
bob <## "started receiving file 1 (test.jpg) from alice"
|
||||
concurrently_
|
||||
(alice <## "completed sending file 1 (test.jpg) to bob")
|
||||
(bob <## "completed receiving file 1 (test.jpg) from alice")
|
||||
Just (CFArgs key nonce) <- J.decode . LB.pack <$> getTermLine bob
|
||||
src <- B.readFile "./tests/fixtures/test.jpg"
|
||||
Right dest <- chatReadFile "./tests/tmp/test.jpg" (strEncode key) (strEncode nonce)
|
||||
LB.toStrict dest `shouldBe` src
|
||||
where
|
||||
cfg = testCfg {inlineFiles = defaultInlineFilesConfig {offerChunks = 100, sendChunks = 100, receiveChunks = 100}}
|
||||
|
||||
testAcceptInlineFileSndCancelDuringTransfer :: HasCallStack => FilePath -> IO ()
|
||||
testAcceptInlineFileSndCancelDuringTransfer =
|
||||
testChatCfg2 cfg aliceProfile bobProfile $ \alice bob -> do
|
||||
@ -1077,10 +1106,10 @@ testXFTPFileTransferEncrypted =
|
||||
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
|
||||
bob ##> "/fr 1 encrypt=on ./tests/tmp/bob/"
|
||||
bob <## "saving file 1 from alice to ./tests/tmp/bob/test.pdf"
|
||||
Just (CFArgs key nonce) <- J.decode . LB.pack <$> getTermLine bob
|
||||
alice <## "completed uploading file 1 (test.pdf) for bob"
|
||||
bob <## "started receiving file 1 (test.pdf) from alice"
|
||||
bob <## "completed receiving file 1 (test.pdf) from alice"
|
||||
Just (CFArgs key nonce) <- J.decode . LB.pack <$> getTermLine bob
|
||||
Right dest <- chatReadFile "./tests/tmp/bob/test.pdf" (strEncode key) (strEncode nonce)
|
||||
LB.length dest `shouldBe` fromIntegral srcLen
|
||||
LB.toStrict dest `shouldBe` src
|
||||
|
Loading…
Reference in New Issue
Block a user