diff --git a/src/Simplex/Chat/Web.hs b/src/Simplex/Chat/Web.hs
index 96640cb430..cf71cffd4f 100644
--- a/src/Simplex/Chat/Web.hs
+++ b/src/Simplex/Chat/Web.hs
@@ -14,7 +14,6 @@ module Simplex.Chat.Web
WebMemberProfile (..),
WebFileInfo (..),
webPreviewWorker,
- renderWebPreviews,
writeCorsConfig,
removeStaleFiles,
channelContentChanged,
@@ -60,7 +59,7 @@ import Simplex.Chat.Messages
)
import Simplex.Chat.Messages.CIContent (ciMsgContent)
import Simplex.Chat.Protocol (MsgContent, MsgRef (..), QuotedMsg (..), isReport)
-import Simplex.Chat.Store.Groups (getGroupOwners, getRelayPublishableGroups, getRelayServedGroups)
+import Simplex.Chat.Store.Groups (getGroupOwners, getRelayPublishableGroups)
import Simplex.Chat.Store.Messages (getGroupWebPreviewItems)
import Simplex.Chat.Store.Shared (getGroupInfo)
import Simplex.Chat.Types
@@ -230,14 +229,6 @@ webPreviewWorker cfg@WebPreviewConfig {webJsonDir, webCorsFile, webUpdateInterva
Right a -> pure (Just a)
Left _ -> go us
-renderWebPreviews :: WebPreviewConfig -> ChatController -> User -> IO ()
-renderWebPreviews cfg@WebPreviewConfig {webJsonDir} cc user = do
- let vr' = chatVRange (config cc)
- createDirectoryIfMissing True webJsonDir
- groups <- withTransaction (chatStore cc) $ \db -> getRelayServedGroups db vr' user
- forM_ [gInfo | gInfo <- groups, hasPublicGroup gInfo] $ \gInfo ->
- void $ renderGroupPreview cfg cc user gInfo
-
renderGroupPreview :: WebPreviewConfig -> ChatController -> User -> GroupInfo -> IO (Maybe (Text, CorsOrigin))
renderGroupPreview WebPreviewConfig {webJsonDir, webPreviewItemCount} cc user gInfo@GroupInfo {groupProfile = gp@GroupProfile {shortDescr = sd, description = wd, publicGroup}} =
case publicGroup of
@@ -250,7 +241,7 @@ renderGroupPreview WebPreviewConfig {webJsonDir, webPreviewItemCount} cc user gI
ts <- getCurrentTime
let rendered = mapMaybe toRenderedItem $ rights items
msgs = map fst rendered
- senders = collectSenders $ map memberToProfile owners <> mapMaybe snd rendered
+ senders = collectSenders $ map memberToProfile owners <> concatMap snd rendered
preview = WebChannelPreview
{ channel = gp,
shortDescription = toFormattedText =<< sd,
@@ -309,14 +300,17 @@ channelRemoved cc gId =
writeTVar corsNeeded True
void $ tryPutTMVar wakeSignal ()
-toRenderedItem :: CChatItem 'CTGroup -> Maybe (WebMessage, Maybe WebMemberProfile)
+toRenderedItem :: CChatItem 'CTGroup -> Maybe (WebMessage, [WebMemberProfile])
toRenderedItem (CChatItem _ ChatItem {chatDir, meta = CIMeta {itemTs, itemTimed, itemForwarded, itemEdited}, content, mentions, formattedText, quotedItem, reactions, file})
| isJust itemTimed = Nothing
| otherwise = case ciMsgContent content of
Just mc | not (isReport mc) ->
let (sender, senderProfile) = case chatDir of
- CIGroupRcv m@GroupMember {memberId} -> (Just memberId, Just $ memberToProfile m)
- _ -> (Nothing, Nothing)
+ CIGroupRcv m@GroupMember {memberId} -> (Just memberId, [memberToProfile m])
+ _ -> (Nothing, [])
+ quotedProfile = case quotedItem of
+ Just CIQuote {chatDir = CIQGroupRcv (Just m)} -> [memberToProfile m]
+ _ -> []
in Just
( WebMessage
{ sender,
@@ -330,7 +324,7 @@ toRenderedItem (CChatItem _ ChatItem {chatDir, meta = CIMeta {itemTs, itemTimed,
forward = if isJust itemForwarded then Just True else Nothing,
edited = itemEdited
},
- senderProfile
+ senderProfile <> quotedProfile
)
_ -> Nothing
diff --git a/website/src/js/channel-preview.jsc b/website/src/js/channel-preview.jsc
index 80a5aa248e..77f0be51d2 100644
--- a/website/src/js/channel-preview.jsc
+++ b/website/src/js/channel-preview.jsc
@@ -1185,7 +1185,7 @@ function renderMessages(container, messages, membersMap, channel) {
}
const col = document.createElement('div');
- const bubble = renderBubble(msg, member, showTail);
+ const bubble = renderBubble(msg, member, showTail, membersMap);
col.appendChild(bubble);
if (msg.reactions && msg.reactions.length > 0) {
@@ -1198,7 +1198,7 @@ function renderMessages(container, messages, membersMap, channel) {
}
}
-function renderBubble(msg, member, showTail) {
+function renderBubble(msg, member, showTail, membersMap) {
const mc = msg.content;
const mediaOnly = (mc.type === 'image' || mc.type === 'video') && !mc.text && !msg.quote && !msg.forward;
const noTailContent = (mc.type === 'image' || mc.type === 'video' || mc.type === 'voice') && !mc.text;
@@ -1225,7 +1225,7 @@ function renderBubble(msg, member, showTail) {
}
if (msg.quote) {
- inner.appendChild(renderQuote(msg.quote));
+ inner.appendChild(renderQuote(msg.quote, membersMap));
}
switch (mc.type) {
@@ -1262,7 +1262,7 @@ function renderBubble(msg, member, showTail) {
return bubble;
}
-function renderQuote(quote) {
+function renderQuote(quote, membersMap) {
const quoteDiv = document.createElement('div');
quoteDiv.className = 'simplex-preview-quote';
@@ -1272,7 +1272,8 @@ function renderQuote(quote) {
if (quote.msgRef && quote.msgRef.memberId) {
const sender = document.createElement('div');
sender.className = 'simplex-preview-quote-sender';
- sender.textContent = quote.msgRef.sent ? 'You' : (quote.msgRef.memberId || '');
+ const quotedMember = membersMap[quote.msgRef.memberId];
+ sender.textContent = quote.msgRef.sent ? 'You' : (quotedMember ? quotedMember.displayName : '');
contentDiv.appendChild(sender);
}
diff --git a/website/src/js/simplex-lib.jsc b/website/src/js/simplex-lib.jsc
index b4b2d86541..4b6319c206 100644
--- a/website/src/js/simplex-lib.jsc
+++ b/website/src/js/simplex-lib.jsc
@@ -23,6 +23,13 @@ function escapeAttr(text) {
.replace(/>/g, ">");
}
+const SAFE_URI_SCHEME = /^(https?:|simplex:|mailto:|tel:)/i;
+
+function safeHref(uri) {
+ if (SAFE_URI_SCHEME.test(uri)) return escapeAttr(uri);
+ return `javascript:void(alert('Potentially malicious link blocked:\\n'+${JSON.stringify(uri)}))`;
+}
+
function getSimplexLinkDescr(linkType) {
switch (linkType) {
case 'contact': return 'SimpleX contact address';
@@ -78,19 +85,20 @@ function renderMarkdown(fts) {
case 'colored':
html += `${escapeHtml(text)}`;
break;
- case 'uri':
+ case 'uri': {
let href = text.startsWith('http://') || text.startsWith('https://') || text.startsWith('simplex:/') ? text : 'https://' + text;
- html += `${escapeHtml(text)}`;
+ html += `${escapeHtml(text)}`;
break;
+ }
case 'hyperLink': {
const { showText, linkUri } = format;
- html += `${escapeHtml(showText ?? linkUri)}`;
+ html += `${escapeHtml(showText ?? linkUri)}`;
break;
}
case 'simplexLink': {
const { showText, linkType, simplexUri, smpHosts } = format;
const linkText = showText ? escapeHtml(showText) : getSimplexLinkDescr(linkType);
- html += `${linkText} (${escapeHtml(viaHost(smpHosts))})`;
+ html += `${linkText} (${escapeHtml(viaHost(smpHosts))})`;
break;
}
case 'command':