Ensure long message trimming is unicode-aware
Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
parent
5b22cd1211
commit
17d8998404
6 changed files with 54 additions and 31 deletions
|
@ -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';
|
||||||
|
@ -55,6 +54,7 @@ import {
|
||||||
getChangesForPropAtTimestamp,
|
getChangesForPropAtTimestamp,
|
||||||
} from '../../util/editHelpers';
|
} from '../../util/editHelpers';
|
||||||
import { getMessageSentTimestamp } from '../../util/getMessageSentTimestamp';
|
import { getMessageSentTimestamp } from '../../util/getMessageSentTimestamp';
|
||||||
|
import { isBodyTooLong, trimBody } from '../../util/longAttachment';
|
||||||
|
|
||||||
const MAX_CONCURRENT_ATTACHMENT_UPLOADS = 5;
|
const MAX_CONCURRENT_ATTACHMENT_UPLOADS = 5;
|
||||||
|
|
||||||
|
@ -587,8 +587,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({
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
@ -2442,7 +2442,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)
|
||||||
),
|
),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
21
ts/util/longAttachment.ts
Normal 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';
|
||||||
|
}
|
Loading…
Reference in a new issue