diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index f678bbe3a9..3e1830a072 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -158,6 +158,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"] @@ -358,9 +374,10 @@ processChatCommand :: VersionRangeChat -> NetworkRequestMode -> ChatCommand -> C processChatCommand vr nm = \case ShowActiveUser -> withUser' $ pure . CRActiveUser CreateActiveUser NewUser {profile, pastTimestamp, userChatRelay} -> 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 @@ -3640,6 +3657,7 @@ processChatCommand vr 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 vr user user' <- updateUser @@ -3725,6 +3743,7 @@ processChatCommand vr 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 @@ -3868,6 +3887,7 @@ processChatCommand vr 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 vr user gProfile incognitoProfile useRelays memberId groupKeys_ publicMemberCount_ diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index d42e833c39..712b7ffaf0 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 (..)) +import Simplex.Chat.Library.Commands (maxProfileImageSize) import Simplex.Chat.Markdown (viewName) import Simplex.Chat.Messages.CIContent (e2eInfoNoPQText, e2eInfoPQText) import Simplex.Chat.Protocol @@ -235,7 +236,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 /