Ensure long message trimming is unicode-aware

This commit is contained in:
Scott Nonnenberg 2024-12-05 02:39:20 +10:00 committed by GitHub
parent 8655244261
commit 08342a9f43
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 54 additions and 31 deletions

View file

@ -31,7 +31,6 @@ import type {
UploadedAttachmentType, UploadedAttachmentType,
} from '../../types/Attachment'; } from '../../types/Attachment';
import { copyCdnFields } from '../../util/attachments'; import { copyCdnFields } from '../../util/attachments';
import { LONG_ATTACHMENT_LIMIT } from '../../types/Message';
import type { RawBodyRange } from '../../types/BodyRange'; import type { RawBodyRange } from '../../types/BodyRange';
import type { EmbeddedContactWithUploadedAvatar } from '../../types/EmbeddedContact'; import type { EmbeddedContactWithUploadedAvatar } from '../../types/EmbeddedContact';
import type { StoryContextType } from '../../types/Util'; import type { StoryContextType } from '../../types/Util';
@ -56,6 +55,7 @@ import {
} from '../../util/editHelpers'; } from '../../util/editHelpers';
import { getMessageSentTimestamp } from '../../util/getMessageSentTimestamp'; import { getMessageSentTimestamp } from '../../util/getMessageSentTimestamp';
import { isSignalConversation } from '../../util/isSignalConversation'; import { isSignalConversation } from '../../util/isSignalConversation';
import { isBodyTooLong, trimBody } from '../../util/longAttachment';
const MAX_CONCURRENT_ATTACHMENT_UPLOADS = 5; const MAX_CONCURRENT_ATTACHMENT_UPLOADS = 5;
@ -595,8 +595,8 @@ async function getMessageSendData({
targetTimestamp, targetTimestamp,
}); });
if (body && body.length > LONG_ATTACHMENT_LIMIT) { if (body && isBodyTooLong(body)) {
body = body.slice(0, LONG_ATTACHMENT_LIMIT); body = trimBody(body);
} }
const uploadQueue = new PQueue({ const uploadQueue = new PQueue({

View file

@ -32,7 +32,6 @@ import {
type ServiceIdString, type ServiceIdString,
} from '../../types/ServiceId'; } from '../../types/ServiceId';
import type { RawBodyRange } from '../../types/BodyRange'; import type { RawBodyRange } from '../../types/BodyRange';
import { LONG_ATTACHMENT_LIMIT } from '../../types/Message';
import { PaymentEventKind } from '../../types/Payment'; import { PaymentEventKind } from '../../types/Payment';
import { MessageRequestResponseEvent } from '../../types/MessageRequestResponseEvent'; import { MessageRequestResponseEvent } from '../../types/MessageRequestResponseEvent';
import type { import type {
@ -139,6 +138,7 @@ import { toAdminKeyBytes } from '../../util/callLinks';
import { getRoomIdFromRootKey } from '../../util/callLinksRingrtc'; import { getRoomIdFromRootKey } from '../../util/callLinksRingrtc';
import { SeenStatus } from '../../MessageSeenStatus'; import { SeenStatus } from '../../MessageSeenStatus';
import { migrateAllMessages } from '../../messages/migrateMessageData'; import { migrateAllMessages } from '../../messages/migrateMessageData';
import { trimBody } from '../../util/longAttachment';
const MAX_CONCURRENCY = 10; const MAX_CONCURRENCY = 10;
@ -2456,7 +2456,7 @@ export class BackupExportStream extends Readable {
text: text:
message.body != null message.body != null
? { ? {
body: message.body?.slice(0, LONG_ATTACHMENT_LIMIT), body: message.body ? trimBody(message.body) : undefined,
bodyRanges: message.bodyRanges?.map(range => bodyRanges: message.bodyRanges?.map(range =>
this.toBodyRange(range) this.toBodyRange(range)
), ),

View file

@ -154,6 +154,7 @@ import {
import { checkOurPniIdentityKey } from '../util/checkOurPniIdentityKey'; import { checkOurPniIdentityKey } from '../util/checkOurPniIdentityKey';
import { CallLinkUpdateSyncType } from '../types/CallLink'; import { CallLinkUpdateSyncType } from '../types/CallLink';
import { bytesToUuid } from '../util/uuidToBytes'; import { bytesToUuid } from '../util/uuidToBytes';
import { isBodyTooLong } from '../util/longAttachment';
const GROUPV2_ID_LENGTH = 32; const GROUPV2_ID_LENGTH = 32;
const RETRY_TIMEOUT = 2 * 60 * 1000; const RETRY_TIMEOUT = 2 * 60 * 1000;
@ -2157,7 +2158,8 @@ export default class MessageReceiver
envelope: ProcessedEnvelope, envelope: ProcessedEnvelope,
sentContainer: ProcessedSent sentContainer: ProcessedSent
) { ) {
log.info('MessageReceiver.handleSentMessage', getEnvelopeId(envelope)); const logId = `MessageReceiver.handleSentMessage/${getEnvelopeId(envelope)}`;
log.info(logId);
logUnexpectedUrgentValue(envelope, 'sentSync'); logUnexpectedUrgentValue(envelope, 'sentSync');
@ -2172,7 +2174,7 @@ export default class MessageReceiver
} = sentContainer; } = sentContainer;
if (!msg) { if (!msg) {
throw new Error('MessageReceiver.handleSentMessage: message was falsey!'); throw new Error(`${logId}: message was falsey!`);
} }
// TODO: DESKTOP-5804 // TODO: DESKTOP-5804
@ -2180,14 +2182,18 @@ export default class MessageReceiver
if (destinationServiceId) { if (destinationServiceId) {
await this.handleEndSession(envelope, destinationServiceId); await this.handleEndSession(envelope, destinationServiceId);
} else { } else {
throw new Error( throw new Error(`${logId}: Cannot end session with falsey destination`);
'MessageReceiver.handleSentMessage: Cannot end session with falsey destination'
);
} }
} }
const message = this.processDecrypted(envelope, msg); const message = this.processDecrypted(envelope, msg);
if (message.body && isBodyTooLong(message.body)) {
const length = Buffer.byteLength(message.body);
this.removeFromCache(envelope);
log.warn(`${logId}: Dropping too-long message. Length: ${length}`);
}
const ev = new SentEvent( const ev = new SentEvent(
{ {
envelopeId: envelope.id, envelopeId: envelope.id,
@ -2487,12 +2493,12 @@ export default class MessageReceiver
envelope: UnsealedEnvelope, envelope: UnsealedEnvelope,
msg: Proto.IDataMessage msg: Proto.IDataMessage
): Promise<void> { ): Promise<void> {
const logId = getEnvelopeId(envelope); const logId = `MessageReceiver.handleDataMessage/${getEnvelopeId(envelope)}`;
log.info('MessageReceiver.handleDataMessage', logId); log.info(logId);
if (getStoriesBlocked() && msg.storyContext) { if (getStoriesBlocked() && msg.storyContext) {
log.info( log.info(
`MessageReceiver.handleDataMessage/${logId}: Dropping incoming dataMessage with storyContext field` `${logId}: Dropping incoming dataMessage with storyContext field`
); );
this.removeFromCache(envelope); this.removeFromCache(envelope);
return; return;
@ -2501,15 +2507,10 @@ export default class MessageReceiver
let p: Promise<void> = Promise.resolve(); let p: Promise<void> = Promise.resolve();
const { sourceServiceId: sourceAci } = envelope; const { sourceServiceId: sourceAci } = envelope;
if (!sourceAci) { if (!sourceAci) {
throw new Error( throw new Error(`${logId}: sourceAci was falsey`);
'MessageReceiver.handleDataMessage: sourceAci was falsey'
);
} }
strictAssert( strictAssert(isAciString(sourceAci), `${logId}: received message from PNI`);
isAciString(sourceAci),
'MessageReceiver.handleDataMessage: received message from PNI'
);
if (this.isInvalidGroupData(msg, envelope)) { if (this.isInvalidGroupData(msg, envelope)) {
this.removeFromCache(envelope); this.removeFromCache(envelope);
@ -2542,10 +2543,10 @@ export default class MessageReceiver
); );
if (isProfileKeyUpdate) { if (isProfileKeyUpdate) {
return this.dispatchAndWait(logId, ev); return this.dispatchAndWait(getEnvelopeId(envelope), ev);
} }
drop(this.dispatchAndWait(logId, ev)); drop(this.dispatchAndWait(getEnvelopeId(envelope), ev));
} }
await p; await p;
@ -2573,13 +2574,17 @@ export default class MessageReceiver
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false; const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
if (groupId && isBlocked) { if (groupId && isBlocked) {
log.warn( log.warn(`${logId}: message ignored; destined for blocked group`);
`Message ${getEnvelopeId(envelope)} ignored; destined for blocked group`
);
this.removeFromCache(envelope); this.removeFromCache(envelope);
return undefined; return undefined;
} }
if (message.body && isBodyTooLong(message.body)) {
const length = Buffer.byteLength(message.body);
this.removeFromCache(envelope);
log.warn(`${logId}: Dropping too-long message. Length: ${length}`);
}
const ev = new MessageEvent( const ev = new MessageEvent(
{ {
envelopeId: envelope.id, envelopeId: envelope.id,

View file

@ -6,8 +6,6 @@ import type { AttachmentType } from './Attachment';
import type { EmbeddedContactType } from './EmbeddedContact'; import type { EmbeddedContactType } from './EmbeddedContact';
import type { IndexableBoolean, IndexablePresence } from './IndexedDB'; import type { IndexableBoolean, IndexablePresence } from './IndexedDB';
export const LONG_ATTACHMENT_LIMIT = 2048;
export function getMentionsRegex(): RegExp { export function getMentionsRegex(): RegExp {
return /\uFFFC/g; return /\uFFFC/g;
} }

View file

@ -50,8 +50,8 @@ import {
} from '../util/getLocalAttachmentUrl'; } from '../util/getLocalAttachmentUrl';
import { encryptLegacyAttachment } from '../util/encryptLegacyAttachment'; import { encryptLegacyAttachment } from '../util/encryptLegacyAttachment';
import { deepClone } from '../util/deepClone'; import { deepClone } from '../util/deepClone';
import { LONG_ATTACHMENT_LIMIT } from './Message';
import * as Bytes from '../Bytes'; import * as Bytes from '../Bytes';
import { isBodyTooLong } from '../util/longAttachment';
export const GROUP = 'group'; export const GROUP = 'group';
export const PRIVATE = 'private'; export const PRIVATE = 'private';
@ -1183,16 +1183,15 @@ export async function migrateBodyAttachmentToDisk(
return message; return message;
} }
if (!message.body || (message.body?.length ?? 0) <= LONG_ATTACHMENT_LIMIT) { if (!message.body || !isBodyTooLong(message.body)) {
return message; return message;
} }
logger.info(`${logId}: Writing bodyAttachment to disk`); logger.info(`${logId}: Writing bodyAttachment to disk`);
const data = Bytes.fromString(message.body);
const bodyAttachment = { const bodyAttachment = {
contentType: LONG_MESSAGE, contentType: LONG_MESSAGE,
...(await writeNewAttachmentData(data)), ...(await writeNewAttachmentData(Bytes.fromString(message.body))),
}; };
return { return {

21
ts/util/longAttachment.ts Normal file
View file

@ -0,0 +1,21 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { unicodeSlice } from './unicodeSlice';
const LONG_ATTACHMENT_LIMIT = 2048;
export function isBodyTooLong(body: string): boolean {
return Buffer.byteLength(body) > LONG_ATTACHMENT_LIMIT;
}
export function trimBody(body: string, length = LONG_ATTACHMENT_LIMIT): string {
const sliced = unicodeSlice(body, 0, length);
if (sliced.length > 0) {
return sliced;
}
// Failover, for degenerate cases
return '\uFFFE';
}