Skip to content

Commit 7e33560

Browse files
authored
fix: remove image section in og message if there is no og image (#1058)
- Removed image section in the OGMessageItemBody if there is no og image - Fixed that safely opens URL to prevent XSS tickets: [CLNP-2924] [CLNP-2843] [CLNP-2924]: https://sendbird.atlassian.net/browse/CLNP-2924?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [CLNP-2843]: https://sendbird.atlassian.net/browse/CLNP-2843?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
1 parent de44ec3 commit 7e33560

File tree

9 files changed

+162
-144
lines changed

9 files changed

+162
-144
lines changed

src/modules/Thread/components/ParentMessageInfo/ParentMessageInfoItem.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
truncateString,
2020
SendableMessageType,
2121
isMultipleFilesMessage,
22+
isFileMessage,
2223
} from '../../../../utils';
2324

2425
import Label, { LabelTypography, LabelColors } from '../../../../ui/Label';
@@ -37,6 +38,7 @@ import { useThreadMessageKindKeySelector } from '../../../Channel/context/hooks/
3738
import { useFileInfoListWithUploaded } from '../../../Channel/context/hooks/useFileInfoListWithUploaded';
3839
import { Colors } from '../../../../utils/color';
3940
import type { OnBeforeDownloadFileMessageType } from '../../../GroupChannel/context/GroupChannelProvider';
41+
import { openURL } from '../../../../utils/utils';
4042

4143
export interface ParentMessageInfoItemProps {
4244
className?: string;
@@ -97,9 +99,7 @@ export default function ParentMessageInfoItem({
9799

98100
// Only for the FileMessageItemBody
99101
const downloadFileWithUrl = () => {
100-
if (message.messageType === 'file') {
101-
window.open((message as FileMessage)?.url);
102-
}
102+
if (isFileMessage(message)) openURL(message.url);
103103
};
104104
const handleOnClickTextButton = onBeforeDownloadFileMessage
105105
? async () => {

src/ui/FileMessageItemBody/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Colors } from '../../utils/color';
1010
import { useMediaQueryContext } from '../../lib/MediaQueryContext';
1111
import useSendbirdStateContext from '../../hooks/useSendbirdStateContext';
1212
import type { OnBeforeDownloadFileMessageType } from '../../modules/GroupChannel/context/GroupChannelProvider';
13+
import { openURL } from '../../utils/utils';
1314

1415
interface Props {
1516
className?: string | Array<string>;
@@ -39,7 +40,7 @@ export default function FileMessageItemBody({
3940
const { isMobile } = useMediaQueryContext();
4041
const truncateMaxNum = truncateLimit || (isMobile ? 20 : null);
4142

42-
const downloadFileWithUrl = () => window.open(message?.url);
43+
const downloadFileWithUrl = () => openURL(message?.url);
4344
const handleOnClickTextButton = onBeforeDownloadFileMessage
4445
? async () => {
4546
try {

src/ui/LinkLabel/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Label, { LabelColors, LabelTypography } from '../Label';
44
import { changeColorToClassName } from '../Label/utils';
55
import './index.scss';
66
import { ObjectValues } from '../../utils/typeHelpers/objectValues';
7+
import { openURL } from '../../utils/utils';
78

89
const http = /https?:\/\//;
910

@@ -32,7 +33,7 @@ export default function LinkLabel({ className = '', src, type, color, children }
3233
onTouchEnd={(e) => {
3334
e.preventDefault();
3435
e.nativeEvent.stopImmediatePropagation();
35-
window.open(url, '_blank', 'noopener,noreferrer');
36+
openURL(url);
3637
}}
3738
>
3839
<Label className="sendbird-link-label__label" type={type} color={color}>

src/ui/OGMessageItemBody/__tests__/OGMessageItemBody.spec.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ describe('ui/OGMessageItemBody', () => {
1212
title: text,
1313
description: text,
1414
url: text,
15-
image: text,
15+
defaultImage: {
16+
url: 'https://image-url.com'
17+
},
1618
},
1719
message: text,
1820
};
@@ -25,10 +27,13 @@ describe('ui/OGMessageItemBody', () => {
2527
});
2628

2729
it('should add .sendbird-og-message-item-body__og-thumbnail__image-empty classname when image is empty', () => {
28-
const message = { message: 'example-text' };
30+
const message = {
31+
message: 'example-text',
32+
ogMetaData: { defaultImage: { url: undefined } },
33+
};
2934
const { container } = render(
3035
<MessageProvider message={message}>
31-
<OGMessageItemBody message={{ message: 'example-text' }} />
36+
<OGMessageItemBody message={message} />
3237
</MessageProvider>
3338
);
3439
expect(container.getElementsByClassName('sendbird-og-message-item-body__og-thumbnail__empty').length).toBe(1);

src/ui/OGMessageItemBody/__tests__/__snapshots__/OGMessageItemBody.spec.js.snap

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,21 @@ exports[`ui/OGMessageItemBody should do a snapshot test of the OGMessageItemBody
1515
</div>
1616
</span>
1717
<div
18-
class="sendbird-og-message-item-body__og-thumbnail
19-
sendbird-og-message-item-body__og-thumbnail__empty
20-
"
18+
class="sendbird-og-message-item-body__og-thumbnail "
2119
>
2220
<div
2321
class="sendbird-og-message-item-body__og-thumbnail__image sendbird-image-renderer"
2422
style="width: 100%; min-width: min(400px, 100%); max-width: 400px;"
2523
>
2624
<div
27-
class="sendbird-og-message-item-body__og-thumbnail__place-holder"
28-
>
29-
<div
30-
class="sendbird-og-message-item-body__og-thumbnail__place-holder__icon sendbird-icon sendbird-icon-thumbnail-none "
31-
role="button"
32-
style="width: 56px; min-width: 56px; height: 56px; min-height: 56px;"
33-
tabindex="0"
34-
>
35-
<test-file-stub />
36-
</div>
37-
</div>
25+
class="sendbird-image-renderer__image"
26+
style="width: 100%; min-width: min(400px, 100%); max-width: 400px; position: absolute; background-repeat: no-repeat; background-position: center; background-size: cover; background-image: url(https://image-url.com);"
27+
/>
28+
<img
29+
alt=""
30+
class="sendbird-image-renderer__hidden-image-loader"
31+
src="https://image-url.com"
32+
/>
3833
</div>
3934
</div>
4035
<div

src/ui/OGMessageItemBody/index.tsx

Lines changed: 118 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import './index.scss';
2-
import React, {
3-
ReactElement,
4-
useContext,
5-
useMemo,
6-
useRef,
7-
} from 'react';
8-
import type { UserMessage } from '@sendbird/chat/message';
2+
import React, { ReactElement, useContext, useEffect, useMemo, useRef } from 'react';
3+
import type { OGImage, OGMetaData, UserMessage } from '@sendbird/chat/message';
94

105
import ImageRenderer from '../ImageRenderer';
116
import Icon, { IconTypes } from '../Icon';
@@ -16,6 +11,7 @@ import TextFragment from '../../modules/Message/components/TextFragment';
1611
import { tokenizeMessage } from '../../modules/Message/utils/tokens/tokenize';
1712
import { OG_MESSAGE_BODY_CLASSNAME } from './consts';
1813
import { useMediaQueryContext } from '../../lib/MediaQueryContext';
14+
import { openURL } from '../../utils/utils';
1915

2016
interface Props {
2117
className?: string | Array<string>;
@@ -34,21 +30,12 @@ export default function OGMessageItemBody({
3430
mouseHover = false,
3531
isMentionEnabled = false,
3632
isReactionEnabled = false,
37-
onMessageHeightChange = () => { /* noop */ },
33+
onMessageHeightChange = () => {
34+
/* noop */
35+
},
3836
}: Props): ReactElement {
39-
const imageRef = useRef<HTMLDivElement>(null);
4037
const { stringSet } = useContext(LocalizationContext);
41-
const { isMobile } = useMediaQueryContext();
4238

43-
const openOGUrl = (): void => {
44-
let url = message?.ogMetaData?.url;
45-
if (url) {
46-
if (!url.startsWith('http://') && !url.startsWith('https://')) {
47-
url = 'https://' + url;
48-
}
49-
window.open(url);
50-
}
51-
};
5239
const isMessageMentioned = isMentionEnabled && message?.mentionedMessageTemplate?.length > 0 && message?.mentionedUsers?.length > 0;
5340
const tokens = useMemo(() => {
5441
if (isMessageMentioned) {
@@ -61,99 +48,125 @@ export default function OGMessageItemBody({
6148
messageText: message?.message,
6249
});
6350
}, [message?.updatedAt, message?.message]);
51+
52+
const openOpenGraphURL = () => openURL(message?.ogMetaData?.url);
53+
6454
return (
65-
<div className={getClassName([
66-
className,
67-
'sendbird-og-message-item-body',
68-
isByMe ? 'outgoing' : 'incoming',
69-
mouseHover ? 'mouse-hover' : '',
70-
(isReactionEnabled && message?.reactions?.length > 0) ? 'reactions' : '',
71-
])}>
72-
<Label
73-
type={LabelTypography.BODY_1}
74-
color={isByMe ? LabelColors.ONCONTENT_1 : LabelColors.ONBACKGROUND_1}
75-
>
55+
<div
56+
className={getClassName([
57+
className,
58+
'sendbird-og-message-item-body',
59+
isByMe ? 'outgoing' : 'incoming',
60+
mouseHover ? 'mouse-hover' : '',
61+
isReactionEnabled && message?.reactions?.length > 0 ? 'reactions' : '',
62+
])}
63+
>
64+
<Label type={LabelTypography.BODY_1} color={isByMe ? LabelColors.ONCONTENT_1 : LabelColors.ONBACKGROUND_1}>
7665
<div className={OG_MESSAGE_BODY_CLASSNAME}>
7766
<TextFragment tokens={tokens} />
78-
{
79-
isEditedMessage(message) && (
80-
<Label
81-
className="sendbird-og-message-item-body__text-bubble__message"
82-
type={LabelTypography.BODY_1}
83-
color={isByMe ? LabelColors.ONCONTENT_2 : LabelColors.ONBACKGROUND_2}
84-
>
85-
{` ${stringSet.MESSAGE_EDITED} `}
86-
</Label>
87-
)
88-
}
67+
{isEditedMessage(message) && (
68+
<Label
69+
className="sendbird-og-message-item-body__text-bubble__message"
70+
type={LabelTypography.BODY_1}
71+
color={isByMe ? LabelColors.ONCONTENT_2 : LabelColors.ONBACKGROUND_2}
72+
>
73+
{` ${stringSet.MESSAGE_EDITED} `}
74+
</Label>
75+
)}
8976
</div>
9077
</Label>
91-
<div
92-
ref={imageRef}
93-
className={`sendbird-og-message-item-body__og-thumbnail
94-
${message?.ogMetaData?.defaultImage?.url ? '' : 'sendbird-og-message-item-body__og-thumbnail__empty'}
95-
`}
96-
onClick={openOGUrl}
97-
>
98-
<ImageRenderer
99-
className="sendbird-og-message-item-body__og-thumbnail__image"
100-
url={message?.ogMetaData?.defaultImage?.url || ''}
101-
alt={message?.ogMetaData?.defaultImage?.alt}
102-
width="100%"
103-
height={isMobile ? '136px' : '240px'}
104-
onLoad={onMessageHeightChange}
105-
onError={() => {
106-
try {
107-
imageRef?.current?.classList?.add('sendbird-og-message-item-body__og-thumbnail__empty');
108-
} catch (error) {
109-
// do nothing
110-
}
111-
}}
112-
defaultComponent={(
113-
<div className="sendbird-og-message-item-body__og-thumbnail__place-holder">
114-
<Icon
115-
className="sendbird-og-message-item-body__og-thumbnail__place-holder__icon"
116-
type={IconTypes.THUMBNAIL_NONE}
117-
width="56px"
118-
height="56px"
119-
/>
120-
</div>
121-
)}
78+
{message.ogMetaData?.defaultImage && (
79+
<OGImageSection
80+
onClick={openOpenGraphURL}
81+
ogImage={message.ogMetaData.defaultImage}
82+
onMessageHeightChange={onMessageHeightChange}
12283
/>
123-
</div>
124-
<div
125-
className="sendbird-og-message-item-body__description"
126-
onClick={openOGUrl}
127-
>
128-
{message?.ogMetaData?.title && (
129-
<Label
130-
className="sendbird-og-message-item-body__description__title"
131-
type={LabelTypography.SUBTITLE_2}
132-
color={LabelColors.ONBACKGROUND_1}
133-
>
134-
{message.ogMetaData.title}
135-
</Label>
136-
)}
137-
{message?.ogMetaData?.description && (
138-
<Label
139-
className="sendbird-og-message-item-body__description__description"
140-
type={LabelTypography.BODY_2}
141-
color={LabelColors.ONBACKGROUND_1}
142-
>
143-
{message.ogMetaData.description}
144-
</Label>
145-
)}
146-
{message?.ogMetaData?.url && (
147-
<Label
148-
className="sendbird-og-message-item-body__description__url"
149-
type={LabelTypography.CAPTION_3}
150-
color={LabelColors.ONBACKGROUND_2}
151-
>
152-
{message.ogMetaData.url}
153-
</Label>
154-
)}
155-
</div>
84+
)}
85+
{message.ogMetaData && (
86+
<OGDescriptionSection onClick={openOpenGraphURL} ogMetaData={message.ogMetaData} onMessageHeightChange={onMessageHeightChange} />
87+
)}
15688
<div className="sendbird-og-message-item-body__cover" />
15789
</div>
15890
);
15991
}
92+
93+
const OGImageSection = (props: { onClick: () => void; ogImage: OGImage; onMessageHeightChange: () => void }) => {
94+
const { onClick, ogImage, onMessageHeightChange } = props;
95+
96+
const imageRef = useRef<HTMLDivElement>(null);
97+
const { isMobile } = useMediaQueryContext();
98+
99+
return (
100+
<div
101+
ref={imageRef}
102+
className={`sendbird-og-message-item-body__og-thumbnail ${ogImage.url ? '' : 'sendbird-og-message-item-body__og-thumbnail__empty'}`}
103+
onClick={() => onClick()}
104+
>
105+
<ImageRenderer
106+
className="sendbird-og-message-item-body__og-thumbnail__image"
107+
url={ogImage.url || ''}
108+
alt={ogImage.alt}
109+
width="100%"
110+
height={isMobile ? '136px' : '240px'}
111+
onLoad={onMessageHeightChange}
112+
onError={() => {
113+
try {
114+
imageRef?.current?.classList?.add('sendbird-og-message-item-body__og-thumbnail__empty');
115+
} catch (error) {
116+
// do nothing
117+
}
118+
}}
119+
defaultComponent={
120+
<div className="sendbird-og-message-item-body__og-thumbnail__place-holder">
121+
<Icon
122+
className="sendbird-og-message-item-body__og-thumbnail__place-holder__icon"
123+
type={IconTypes.THUMBNAIL_NONE}
124+
width="56px"
125+
height="56px"
126+
/>
127+
</div>
128+
}
129+
/>
130+
</div>
131+
);
132+
};
133+
134+
const OGDescriptionSection = (props: { onClick: () => void; ogMetaData: OGMetaData; onMessageHeightChange: () => void }) => {
135+
const { onClick, ogMetaData, onMessageHeightChange } = props;
136+
137+
useEffect(() => {
138+
onMessageHeightChange();
139+
}, [ogMetaData.title, ogMetaData.description, ogMetaData.url]);
140+
141+
return (
142+
<div className="sendbird-og-message-item-body__description" onClick={() => onClick()}>
143+
{ogMetaData.title && (
144+
<Label
145+
className="sendbird-og-message-item-body__description__title"
146+
type={LabelTypography.SUBTITLE_2}
147+
color={LabelColors.ONBACKGROUND_1}
148+
>
149+
{ogMetaData.title}
150+
</Label>
151+
)}
152+
{ogMetaData.description && (
153+
<Label
154+
className="sendbird-og-message-item-body__description__description"
155+
type={LabelTypography.BODY_2}
156+
color={LabelColors.ONBACKGROUND_1}
157+
>
158+
{ogMetaData.description}
159+
</Label>
160+
)}
161+
{ogMetaData.url && (
162+
<Label
163+
className="sendbird-og-message-item-body__description__url"
164+
type={LabelTypography.CAPTION_3}
165+
color={LabelColors.ONBACKGROUND_2}
166+
>
167+
{ogMetaData.url}
168+
</Label>
169+
)}
170+
</div>
171+
);
172+
};

0 commit comments

Comments
 (0)