Trim body on export if body attachment remains

This commit is contained in:
trevor-signal
2025-11-24 11:58:42 -05:00
committed by GitHub
parent cff3796ba7
commit b3a93ffab4
2 changed files with 51 additions and 42 deletions

View File

@@ -161,7 +161,11 @@ import {
} from '../../util/callLinksRingrtc.node.js'; } from '../../util/callLinksRingrtc.node.js';
import { SeenStatus } from '../../MessageSeenStatus.std.js'; import { SeenStatus } from '../../MessageSeenStatus.std.js';
import { migrateAllMessages } from '../../messages/migrateMessageData.preload.js'; import { migrateAllMessages } from '../../messages/migrateMessageData.preload.js';
import { isBodyTooLong, trimBody } from '../../util/longAttachment.std.js'; import {
isBodyTooLong,
MAX_MESSAGE_BODY_BYTE_LENGTH,
trimBody,
} from '../../util/longAttachment.std.js';
import { generateBackupsSubscriberData } from '../../util/backupSubscriptionData.preload.js'; import { generateBackupsSubscriberData } from '../../util/backupSubscriptionData.preload.js';
import { import {
getEnvironment, getEnvironment,
@@ -195,7 +199,7 @@ const FLUSH_TIMEOUT = 30 * MINUTE;
// Threshold for reporting slow flushes // Threshold for reporting slow flushes
const REPORTING_THRESHOLD = SECOND; const REPORTING_THRESHOLD = SECOND;
const BACKUP_LONG_ATTACHMENT_TEXT_LIMIT = 128 * KIBIBYTE; const MAX_BACKUP_MESSAGE_BODY_BYTE_LENGTH = 128 * KIBIBYTE;
const BACKUP_QUOTE_BODY_LIMIT = 2048; const BACKUP_QUOTE_BODY_LIMIT = 2048;
type GetRecipientIdOptionsType = type GetRecipientIdOptionsType =
@@ -2986,7 +2990,7 @@ export class BackupExportStream extends Readable {
}): Promise<Backups.IStandardMessage> { }): Promise<Backups.IStandardMessage> {
if ( if (
message.body && message.body &&
isBodyTooLong(message.body, BACKUP_LONG_ATTACHMENT_TEXT_LIMIT) isBodyTooLong(message.body, MAX_BACKUP_MESSAGE_BODY_BYTE_LENGTH)
) { ) {
log.warn(`${message.timestamp}: Message body is too long; will truncate`); log.warn(`${message.timestamp}: Message body is too long; will truncate`);
} }
@@ -3005,26 +3009,7 @@ export class BackupExportStream extends Readable {
}) })
) )
: undefined, : undefined,
longText: ...(await this.#toTextAndLongTextFields(message)),
// We only include the bodyAttachment if it's not downloaded; otherwise all text
// is inlined
message.bodyAttachment && !isDownloaded(message.bodyAttachment)
? await this.#processAttachment({
attachment: message.bodyAttachment,
messageReceivedAt: message.received_at,
})
: undefined,
text:
message.body != null
? {
body: message.body
? trimBody(message.body, BACKUP_LONG_ATTACHMENT_TEXT_LIMIT)
: undefined,
bodyRanges: message.bodyRanges?.map(range =>
this.#toBodyRange(range)
),
}
: undefined,
linkPreview: message.preview linkPreview: message.preview
? await Promise.all( ? await Promise.all(
message.preview.map(async preview => { message.preview.map(async preview => {
@@ -3067,25 +3052,51 @@ export class BackupExportStream extends Readable {
if (message.storyReaction) { if (message.storyReaction) {
result.emoji = message.storyReaction.emoji; result.emoji = message.storyReaction.emoji;
} else { } else {
result.textReply = { result.textReply = await this.#toTextAndLongTextFields(message);
longText: message.bodyAttachment }
return result;
}
async #toTextAndLongTextFields(
message: Pick<
MessageAttributesType,
'bodyAttachment' | 'body' | 'bodyRanges' | 'received_at'
>
): Promise<{
longText: Backups.IFilePointer | undefined;
text: Backups.IText | undefined;
}> {
const includeLongTextAttachment =
message.bodyAttachment && !isDownloaded(message.bodyAttachment);
const includeText =
Boolean(message.body) || Boolean(message.bodyRanges?.length);
return {
longText:
// We only include the bodyAttachment if it's not downloaded; otherwise all text
// is inlined
includeLongTextAttachment && message.bodyAttachment
? await this.#processAttachment({ ? await this.#processAttachment({
attachment: message.bodyAttachment, attachment: message.bodyAttachment,
messageReceivedAt: message.received_at, messageReceivedAt: message.received_at,
}) })
: undefined, : undefined,
text: text: includeText
message.body != null ? {
? { body: message.body
body: message.body ? trimBody(message.body) : undefined, ? trimBody(
bodyRanges: message.bodyRanges?.map(range => message.body,
this.#toBodyRange(range) includeLongTextAttachment
), ? MAX_MESSAGE_BODY_BYTE_LENGTH
} : MAX_BACKUP_MESSAGE_BODY_BYTE_LENGTH
: undefined, )
}; : undefined,
} bodyRanges: message.bodyRanges?.map(range =>
return result; this.#toBodyRange(range)
),
}
: undefined,
};
} }
async #toViewOnceMessage({ async #toViewOnceMessage({

View File

@@ -299,9 +299,7 @@ describe('backup/attachments', () => {
} }
); );
}); });
it('includes bodyAttachment if it has not downloaded', async () => { it('includes bodyAttachment if it has not downloaded, and truncates body to 2 KIB', async () => {
const truncatedBody = 'a'.repeat(2 * KIBIBYTE);
const attachment = omit( const attachment = omit(
composeAttachment(1, { composeAttachment(1, {
contentType: LONG_MESSAGE, contentType: LONG_MESSAGE,
@@ -319,13 +317,13 @@ describe('backup/attachments', () => {
await asymmetricRoundtripHarness( await asymmetricRoundtripHarness(
[ [
composeMessage(1, { composeMessage(1, {
body: truncatedBody, body: 'a'.repeat(3 * KIBIBYTE),
bodyAttachment: attachment, bodyAttachment: attachment,
}), }),
], ],
[ [
composeMessage(1, { composeMessage(1, {
body: truncatedBody, body: 'a'.repeat(2 * KIBIBYTE),
bodyAttachment: attachment, bodyAttachment: attachment,
}), }),
], ],