Reject reactions with invalid number of graphemes
This commit is contained in:
parent
95482fbf31
commit
f615b1a75f
5 changed files with 92 additions and 2 deletions
|
@ -11,6 +11,7 @@ import * as refreshSenderCertificate from './refreshSenderCertificate';
|
|||
import { SenderCertificateMode } from './metadata/SecretSessionCipher';
|
||||
import { routineProfileRefresh } from './routineProfileRefresh';
|
||||
import { isMoreRecentThan, isOlderThan } from './util/timestamp';
|
||||
import { isValidReactionEmoji } from './reactions/isValidReactionEmoji';
|
||||
|
||||
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
|
||||
|
||||
|
@ -2601,6 +2602,13 @@ export async function startApp(): Promise<void> {
|
|||
);
|
||||
|
||||
const { reaction } = data.message;
|
||||
|
||||
if (!isValidReactionEmoji(reaction.emoji)) {
|
||||
window.log.warn('Received an invalid reaction emoji. Dropping it');
|
||||
confirm();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
window.log.info(
|
||||
'Queuing incoming reaction for',
|
||||
reaction.targetTimestamp
|
||||
|
@ -2894,6 +2902,13 @@ export async function startApp(): Promise<void> {
|
|||
);
|
||||
|
||||
const { reaction } = data.message;
|
||||
|
||||
if (!isValidReactionEmoji(reaction.emoji)) {
|
||||
window.log.warn('Received an invalid reaction emoji. Dropping it');
|
||||
event.confirm();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
window.log.info('Queuing sent reaction for', reaction.targetTimestamp);
|
||||
const reactionModel = window.Whisper.Reactions.add({
|
||||
emoji: reaction.emoji,
|
||||
|
|
22
ts/reactions/isValidReactionEmoji.ts
Normal file
22
ts/reactions/isValidReactionEmoji.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import emojiRegex from 'emoji-regex/es2015/RGI_Emoji';
|
||||
import { getGraphemes } from '../util/grapheme';
|
||||
import { take, size } from '../util/iterables';
|
||||
|
||||
export function isValidReactionEmoji(value: unknown): value is string {
|
||||
if (typeof value !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is effectively `countGraphemes(value) === 1`, but doesn't require iterating
|
||||
// through an extremely long string.
|
||||
const graphemes = getGraphemes(value);
|
||||
const truncatedGraphemes = take(graphemes, 2);
|
||||
if (size(truncatedGraphemes) !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return emojiRegex().test(value);
|
||||
}
|
31
ts/test-both/reactions/isValidReactionEmoji_test.ts
Normal file
31
ts/test-both/reactions/isValidReactionEmoji_test.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { isValidReactionEmoji } from '../../reactions/isValidReactionEmoji';
|
||||
|
||||
describe('isValidReactionEmoji', () => {
|
||||
it('returns false for non-strings', () => {
|
||||
assert.isFalse(isValidReactionEmoji(undefined));
|
||||
assert.isFalse(isValidReactionEmoji(null));
|
||||
assert.isFalse(isValidReactionEmoji(1));
|
||||
});
|
||||
|
||||
it("returns false for strings that aren't a single emoji", () => {
|
||||
assert.isFalse(isValidReactionEmoji(''));
|
||||
|
||||
assert.isFalse(isValidReactionEmoji('a'));
|
||||
assert.isFalse(isValidReactionEmoji('abc'));
|
||||
|
||||
assert.isFalse(isValidReactionEmoji('💩💩'));
|
||||
|
||||
assert.isFalse(isValidReactionEmoji('🇸'));
|
||||
assert.isFalse(isValidReactionEmoji(''));
|
||||
});
|
||||
|
||||
it('returns true for strings that are exactly 1 emoji', () => {
|
||||
assert.isTrue(isValidReactionEmoji('🇺🇸'));
|
||||
assert.isTrue(isValidReactionEmoji('👩❤️👩'));
|
||||
});
|
||||
});
|
|
@ -3,9 +3,26 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { count } from '../../util/grapheme';
|
||||
import { getGraphemes, count } from '../../util/grapheme';
|
||||
|
||||
describe('grapheme utilities', () => {
|
||||
describe('getGraphemes', () => {
|
||||
it('returns extended graphemes in a string', () => {
|
||||
assert.deepEqual([...getGraphemes('')], []);
|
||||
assert.deepEqual([...getGraphemes('hello')], [...'hello']);
|
||||
assert.deepEqual(
|
||||
[...getGraphemes('Bokmål')],
|
||||
['B', 'o', 'k', 'm', 'å', 'l']
|
||||
);
|
||||
|
||||
assert.deepEqual([...getGraphemes('💩💩💩')], ['💩', '💩', '💩']);
|
||||
assert.deepEqual([...getGraphemes('👩❤️👩')], ['👩❤️👩']);
|
||||
assert.deepEqual([...getGraphemes('👌🏽👌🏾👌🏿')], ['👌🏽', '👌🏾', '👌🏿']);
|
||||
|
||||
assert.deepEqual([...getGraphemes('L̷̳͔̲͝Ģ̵̮̯̤̩̙͍̬̟͉̹̘̹͍͈̮̦̰̣͟͝O̶̴̮̻̮̗͘͡!̴̷̟͓͓')], ['L̷̳͔̲͝', 'Ģ̵̮̯̤̩̙͍̬̟͉̹̘̹͍͈̮̦̰̣͟͝', 'O̶̴̮̻̮̗͘͡', '!̴̷̟͓͓']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('count', () => {
|
||||
it('returns the number of extended graphemes in a string (not necessarily the length)', () => {
|
||||
// These tests modified [from iOS][0].
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { size } from './iterables';
|
||||
import { map, size } from './iterables';
|
||||
|
||||
export function getGraphemes(str: string): Iterable<string> {
|
||||
const segments = new Intl.Segmenter().segment(str);
|
||||
return map(segments, s => s.segment);
|
||||
}
|
||||
|
||||
export function count(str: string): number {
|
||||
const segments = new Intl.Segmenter().segment(str);
|
||||
|
|
Loading…
Reference in a new issue