diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index df5ef58c10..646488c39d 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -162,6 +162,22 @@ checkProfileImageSize = mapM_ $ \(ImageData t) -> let size = T.length t in when (size > maxProfileImageSize) $ throwCmdError $ "Profile image is too large " <> show size +checkProfileSize :: Profile -> CM () +checkProfileSize profile = do + vr <- chatVersionRange + let info = ChatMessage {chatVRange = vr, msgId = Nothing, chatMsgEvent = XInfo profile} + case encodeChatMessage maxEncodedInfoLength info of + ECMEncoded _ -> pure () + ECMLarge -> throwCmdError "Profile is too large" + +checkGroupProfileSize :: GroupProfile -> CM () +checkGroupProfileSize gProfile = do + vr <- chatVersionRange + let info = ChatMessage {chatVRange = vr, msgId = Nothing, chatMsgEvent = XGrpInfo gProfile} + case encodeChatMessage maxEncodedInfoLength info of + ECMEncoded _ -> pure () + ECMLarge -> throwCmdError "Group profile is too large" + imageExtensions :: [String] imageExtensions = [".jpg", ".jpeg", ".png", ".gif"] @@ -377,9 +393,10 @@ processChatCommand :: StoreCxt -> NetworkRequestMode -> ChatCommand -> CM ChatRe processChatCommand cxt nm = \case ShowActiveUser -> withUser' $ pure . CRActiveUser CreateActiveUser NewUser {profile, pastTimestamp, userChatRelay, clientService} -> do - forM_ profile $ \Profile {displayName, image} -> do + forM_ profile $ \p@Profile {displayName, image} -> do checkValidName displayName checkProfileImageSize image + checkProfileSize p p@Profile {displayName} <- liftIO $ maybe generateRandomProfile pure profile u <- asks currentUser users <- withFastStore' getUsers @@ -3834,6 +3851,7 @@ processChatCommand cxt nm = \case | otherwise = do when (n /= n') $ checkValidName n' checkProfileImageSize img' + checkProfileSize p' -- read contacts before user update to correctly merge preferences contacts <- withFastStore' $ \db -> getUserContacts db cxt user user' <- updateUser @@ -3923,6 +3941,7 @@ processChatCommand cxt nm = \case assertUserGroupRole gInfo GROwner when (n /= n') $ checkValidName n' checkProfileImageSize img' + checkGroupProfileSize p' gInfo' <- withStore $ \db -> updateGroupProfile db user gInfo p' msg <- case businessChat of Just BusinessChatInfo {businessId} -> do @@ -4066,6 +4085,7 @@ processChatCommand cxt nm = \case newGroup user incognito gProfile@GroupProfile {displayName, image} useRelays memberId groupKeys_ publicMemberCount_ = do checkValidName displayName checkProfileImageSize image + checkGroupProfileSize gProfile -- [incognito] generate incognito profile for group membership incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing withFastStore $ \db -> createNewGroup db cxt user gProfile incognitoProfile useRelays memberId groupKeys_ publicMemberCount_ diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 12511ea53b..800850ce0b 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -24,6 +24,7 @@ import Data.Maybe (fromMaybe) import Data.String import qualified Data.Text as T import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), mkStoreCxt) +import Simplex.Chat.Library.Commands (maxProfileImageSize) import Simplex.Chat.Markdown (viewName) import Simplex.Chat.Messages.CIContent (e2eInfoNoPQText, e2eInfoPQText) import Simplex.Chat.Protocol @@ -241,7 +242,9 @@ genProfileImg = do g <- C.newRandom atomically $ B64.encode <$> C.randomBytes lrgLen g where - lrgLen = maxEncodedInfoLength * 3 `div` 4 - 420 + -- raw bytes that base64-encode to fit maxProfileImageSize when prefixed with "data:image/png;base64," + lrgLen = (maxProfileImageSize - imagePrefixLen) * 3 `div` 4 - 1 + imagePrefixLen = 22 -- PQ combinators /