diff --git a/ts/jobs/helpers/sendNormalMessage.ts b/ts/jobs/helpers/sendNormalMessage.ts index db376df73ab1..1fec8652c974 100644 --- a/ts/jobs/helpers/sendNormalMessage.ts +++ b/ts/jobs/helpers/sendNormalMessage.ts @@ -31,7 +31,6 @@ import type { UploadedAttachmentType, } from '../../types/Attachment'; import { copyCdnFields } from '../../util/attachments'; -import { LONG_ATTACHMENT_LIMIT } from '../../types/Message'; import type { RawBodyRange } from '../../types/BodyRange'; import type { EmbeddedContactWithUploadedAvatar } from '../../types/EmbeddedContact'; import type { StoryContextType } from '../../types/Util'; @@ -56,6 +55,7 @@ import { } from '../../util/editHelpers'; import { getMessageSentTimestamp } from '../../util/getMessageSentTimestamp'; import { isSignalConversation } from '../../util/isSignalConversation'; +import { isBodyTooLong, trimBody } from '../../util/longAttachment'; const MAX_CONCURRENT_ATTACHMENT_UPLOADS = 5; @@ -595,8 +595,8 @@ async function getMessageSendData({ targetTimestamp, }); - if (body && body.length > LONG_ATTACHMENT_LIMIT) { - body = body.slice(0, LONG_ATTACHMENT_LIMIT); + if (body && isBodyTooLong(body)) { + body = trimBody(body); } const uploadQueue = new PQueue({ diff --git a/ts/services/backups/export.ts b/ts/services/backups/export.ts index 666c8e8aeb51..44327aeb8b94 100644 --- a/ts/services/backups/export.ts +++ b/ts/services/backups/export.ts @@ -32,7 +32,6 @@ import { type ServiceIdString, } from '../../types/ServiceId'; import type { RawBodyRange } from '../../types/BodyRange'; -import { LONG_ATTACHMENT_LIMIT } from '../../types/Message'; import { PaymentEventKind } from '../../types/Payment'; import { MessageRequestResponseEvent } from '../../types/MessageRequestResponseEvent'; import type { @@ -139,6 +138,7 @@ import { toAdminKeyBytes } from '../../util/callLinks'; import { getRoomIdFromRootKey } from '../../util/callLinksRingrtc'; import { SeenStatus } from '../../MessageSeenStatus'; import { migrateAllMessages } from '../../messages/migrateMessageData'; +import { trimBody } from '../../util/longAttachment'; const MAX_CONCURRENCY = 10; @@ -2456,7 +2456,7 @@ export class BackupExportStream extends Readable { text: message.body != null ? { - body: message.body?.slice(0, LONG_ATTACHMENT_LIMIT), + body: message.body ? trimBody(message.body) : undefined, bodyRanges: message.bodyRanges?.map(range => this.toBodyRange(range) ), diff --git a/ts/textsecure/MessageReceiver.ts b/ts/textsecure/MessageReceiver.ts index ab37f09409cd..0785f3c21e17 100644 --- a/ts/textsecure/MessageReceiver.ts +++ b/ts/textsecure/MessageReceiver.ts @@ -154,6 +154,7 @@ import { import { checkOurPniIdentityKey } from '../util/checkOurPniIdentityKey'; import { CallLinkUpdateSyncType } from '../types/CallLink'; import { bytesToUuid } from '../util/uuidToBytes'; +import { isBodyTooLong } from '../util/longAttachment'; const GROUPV2_ID_LENGTH = 32; const RETRY_TIMEOUT = 2 * 60 * 1000; @@ -2157,7 +2158,8 @@ export default class MessageReceiver envelope: ProcessedEnvelope, sentContainer: ProcessedSent ) { - log.info('MessageReceiver.handleSentMessage', getEnvelopeId(envelope)); + const logId = `MessageReceiver.handleSentMessage/${getEnvelopeId(envelope)}`; + log.info(logId); logUnexpectedUrgentValue(envelope, 'sentSync'); @@ -2172,7 +2174,7 @@ export default class MessageReceiver } = sentContainer; if (!msg) { - throw new Error('MessageReceiver.handleSentMessage: message was falsey!'); + throw new Error(`${logId}: message was falsey!`); } // TODO: DESKTOP-5804 @@ -2180,14 +2182,18 @@ export default class MessageReceiver if (destinationServiceId) { await this.handleEndSession(envelope, destinationServiceId); } else { - throw new Error( - 'MessageReceiver.handleSentMessage: Cannot end session with falsey destination' - ); + throw new Error(`${logId}: Cannot end session with falsey destination`); } } 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( { envelopeId: envelope.id, @@ -2487,12 +2493,12 @@ export default class MessageReceiver envelope: UnsealedEnvelope, msg: Proto.IDataMessage ): Promise { - const logId = getEnvelopeId(envelope); - log.info('MessageReceiver.handleDataMessage', logId); + const logId = `MessageReceiver.handleDataMessage/${getEnvelopeId(envelope)}`; + log.info(logId); if (getStoriesBlocked() && msg.storyContext) { log.info( - `MessageReceiver.handleDataMessage/${logId}: Dropping incoming dataMessage with storyContext field` + `${logId}: Dropping incoming dataMessage with storyContext field` ); this.removeFromCache(envelope); return; @@ -2501,15 +2507,10 @@ export default class MessageReceiver let p: Promise = Promise.resolve(); const { sourceServiceId: sourceAci } = envelope; if (!sourceAci) { - throw new Error( - 'MessageReceiver.handleDataMessage: sourceAci was falsey' - ); + throw new Error(`${logId}: sourceAci was falsey`); } - strictAssert( - isAciString(sourceAci), - 'MessageReceiver.handleDataMessage: received message from PNI' - ); + strictAssert(isAciString(sourceAci), `${logId}: received message from PNI`); if (this.isInvalidGroupData(msg, envelope)) { this.removeFromCache(envelope); @@ -2542,10 +2543,10 @@ export default class MessageReceiver ); 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; @@ -2573,13 +2574,17 @@ export default class MessageReceiver const isBlocked = groupId ? this.isGroupBlocked(groupId) : false; if (groupId && isBlocked) { - log.warn( - `Message ${getEnvelopeId(envelope)} ignored; destined for blocked group` - ); + log.warn(`${logId}: message ignored; destined for blocked group`); this.removeFromCache(envelope); 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( { envelopeId: envelope.id, diff --git a/ts/types/Message.ts b/ts/types/Message.ts index 81e25c920ac2..2114ca8eb6e2 100644 --- a/ts/types/Message.ts +++ b/ts/types/Message.ts @@ -6,8 +6,6 @@ import type { AttachmentType } from './Attachment'; import type { EmbeddedContactType } from './EmbeddedContact'; import type { IndexableBoolean, IndexablePresence } from './IndexedDB'; -export const LONG_ATTACHMENT_LIMIT = 2048; - export function getMentionsRegex(): RegExp { return /\uFFFC/g; } diff --git a/ts/types/Message2.ts b/ts/types/Message2.ts index 337966b471f6..19d9a327d6b8 100644 --- a/ts/types/Message2.ts +++ b/ts/types/Message2.ts @@ -50,8 +50,8 @@ import { } from '../util/getLocalAttachmentUrl'; import { encryptLegacyAttachment } from '../util/encryptLegacyAttachment'; import { deepClone } from '../util/deepClone'; -import { LONG_ATTACHMENT_LIMIT } from './Message'; import * as Bytes from '../Bytes'; +import { isBodyTooLong } from '../util/longAttachment'; export const GROUP = 'group'; export const PRIVATE = 'private'; @@ -1183,16 +1183,15 @@ export async function migrateBodyAttachmentToDisk( return message; } - if (!message.body || (message.body?.length ?? 0) <= LONG_ATTACHMENT_LIMIT) { + if (!message.body || !isBodyTooLong(message.body)) { return message; } logger.info(`${logId}: Writing bodyAttachment to disk`); - const data = Bytes.fromString(message.body); const bodyAttachment = { contentType: LONG_MESSAGE, - ...(await writeNewAttachmentData(data)), + ...(await writeNewAttachmentData(Bytes.fromString(message.body))), }; return { diff --git a/ts/util/longAttachment.ts b/ts/util/longAttachment.ts new file mode 100644 index 000000000000..ff7e1b9e3d8f --- /dev/null +++ b/ts/util/longAttachment.ts @@ -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'; +}