mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-04-02 00:07:56 +01:00
Improve export handling of body ranges and storyReactions
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
@@ -32,7 +32,10 @@ import {
|
||||
type AciString,
|
||||
type ServiceIdString,
|
||||
} from '../../types/ServiceId.std.js';
|
||||
import type { RawBodyRange } from '../../types/BodyRange.std.js';
|
||||
import {
|
||||
bodyRangeSchema,
|
||||
type RawBodyRange,
|
||||
} from '../../types/BodyRange.std.js';
|
||||
import { PaymentEventKind } from '../../types/Payment.std.js';
|
||||
import { MessageRequestResponseEvent } from '../../types/MessageRequestResponseEvent.std.js';
|
||||
import type {
|
||||
@@ -183,6 +186,7 @@ import { ChatFolderType } from '../../types/ChatFolder.std.js';
|
||||
import { expiresTooSoonForBackup } from './util/expiration.std.js';
|
||||
import type { PinnedMessage } from '../../types/PinnedMessage.std.js';
|
||||
import type { ThemeType } from '../../util/preload.preload.js';
|
||||
import { safeParseStrict } from '../../util/schemas.std.js';
|
||||
|
||||
const { isNumber } = lodash;
|
||||
|
||||
@@ -1675,7 +1679,7 @@ export class BackupExportStream extends Readable {
|
||||
state,
|
||||
};
|
||||
}
|
||||
} else if (message.storyReplyContext) {
|
||||
} else if (message.storyReplyContext || message.storyReaction) {
|
||||
result.directStoryReplyMessage = await this.#toDirectStoryReplyMessage({
|
||||
message,
|
||||
});
|
||||
@@ -2787,9 +2791,7 @@ export class BackupExportStream extends Readable {
|
||||
quote.text != null
|
||||
? {
|
||||
body: trimBody(quote.text, BACKUP_QUOTE_BODY_LIMIT),
|
||||
bodyRanges: quote.bodyRanges?.map(range =>
|
||||
this.#toBodyRange(range)
|
||||
),
|
||||
bodyRanges: this.#toBodyRanges(quote.bodyRanges),
|
||||
}
|
||||
: null,
|
||||
attachments: await Promise.all(
|
||||
@@ -2814,22 +2816,45 @@ export class BackupExportStream extends Readable {
|
||||
};
|
||||
}
|
||||
|
||||
#toBodyRange(range: RawBodyRange): Backups.IBodyRange {
|
||||
return {
|
||||
start: range.start,
|
||||
length: range.length,
|
||||
#toBodyRange(range: RawBodyRange): Backups.IBodyRange | null {
|
||||
const { data: parsedRange, error } = safeParseStrict(
|
||||
bodyRangeSchema,
|
||||
range
|
||||
);
|
||||
|
||||
...('mentionAci' in range
|
||||
if (error) {
|
||||
log.warn('toBodyRange: Dropping invalid body range', toLogFormat(error));
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
start: parsedRange.start,
|
||||
length: parsedRange.length,
|
||||
|
||||
...('mentionAci' in parsedRange
|
||||
? {
|
||||
mentionAci: this.#aciToBytes(range.mentionAci),
|
||||
mentionAci: this.#aciToBytes(parsedRange.mentionAci),
|
||||
}
|
||||
: {
|
||||
// Numeric values are compatible between backup and message protos
|
||||
style: range.style,
|
||||
style: parsedRange.style,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
#toBodyRanges(
|
||||
ranges: ReadonlyArray<RawBodyRange> | undefined
|
||||
): Array<Backups.IBodyRange> | undefined {
|
||||
if (!ranges?.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result = ranges
|
||||
.map(range => this.#toBodyRange(range))
|
||||
.filter(isNotNil);
|
||||
return result.length > 0 ? result : undefined;
|
||||
}
|
||||
|
||||
#getMessageAttachmentFlag(
|
||||
message: Pick<MessageAttributesType, 'body'>,
|
||||
attachment: AttachmentType
|
||||
@@ -3194,8 +3219,8 @@ export class BackupExportStream extends Readable {
|
||||
}> {
|
||||
const includeLongTextAttachment =
|
||||
message.bodyAttachment && !isDownloaded(message.bodyAttachment);
|
||||
const includeText =
|
||||
Boolean(message.body) || Boolean(message.bodyRanges?.length);
|
||||
const bodyRanges = this.#toBodyRanges(message.bodyRanges);
|
||||
const includeText = Boolean(message.body) || Boolean(bodyRanges?.length);
|
||||
|
||||
return {
|
||||
longText:
|
||||
@@ -3217,9 +3242,7 @@ export class BackupExportStream extends Readable {
|
||||
: MAX_BACKUP_MESSAGE_BODY_BYTE_LENGTH
|
||||
)
|
||||
: undefined,
|
||||
bodyRanges: message.bodyRanges?.map(range =>
|
||||
this.#toBodyRange(range)
|
||||
),
|
||||
bodyRanges,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ import { IMAGE_PNG, TEXT_ATTACHMENT } from '../../types/MIME.std.js';
|
||||
import { MY_STORY_ID } from '../../types/Stories.std.js';
|
||||
import { generateAttachmentKeys } from '../../AttachmentCrypto.node.js';
|
||||
import { itemStorage } from '../../textsecure/Storage.preload.js';
|
||||
import { BodyRange } from '../../types/BodyRange.std.js';
|
||||
|
||||
const CONTACT_A = generateAci();
|
||||
const CONTACT_B = generateAci();
|
||||
@@ -607,7 +608,7 @@ describe('backup/bubble messages', () => {
|
||||
await asymmetricRoundtripHarness(
|
||||
[
|
||||
{
|
||||
conversationId: gv1.id,
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'incoming',
|
||||
received_at: 3,
|
||||
@@ -625,6 +626,62 @@ describe('backup/bubble messages', () => {
|
||||
[]
|
||||
);
|
||||
});
|
||||
it('drops invalid body ranges', async () => {
|
||||
await asymmetricRoundtripHarness(
|
||||
[
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'incoming',
|
||||
received_at: 3,
|
||||
received_at_ms: 3,
|
||||
sent_at: 3,
|
||||
timestamp: 3,
|
||||
sourceServiceId: CONTACT_A,
|
||||
body: 'd',
|
||||
bodyRanges: [
|
||||
{
|
||||
start: 0,
|
||||
length: 1,
|
||||
// @ts-expect-error invalid data
|
||||
style: undefined,
|
||||
},
|
||||
{
|
||||
start: 1,
|
||||
length: 0,
|
||||
style: BodyRange.Style.BOLD,
|
||||
},
|
||||
],
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'incoming',
|
||||
received_at: 3,
|
||||
received_at_ms: 3,
|
||||
sent_at: 3,
|
||||
timestamp: 3,
|
||||
sourceServiceId: CONTACT_A,
|
||||
body: 'd',
|
||||
bodyRanges: [
|
||||
{
|
||||
start: 1,
|
||||
length: 0,
|
||||
style: BodyRange.Style.BOLD,
|
||||
},
|
||||
],
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('drops messages that expire soon', async () => {
|
||||
await asymmetricRoundtripHarness(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
|
||||
import lodash from 'lodash';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { SignalService as Proto } from '../protobuf/index.std.js';
|
||||
import { createLogger } from '../logging/log.std.js';
|
||||
@@ -15,7 +16,8 @@ import {
|
||||
SNIPPET_TRUNCATION_PLACEHOLDER,
|
||||
} from '../util/search.std.js';
|
||||
import { assertDev } from '../util/assert.std.js';
|
||||
import type { AciString } from './ServiceId.std.js';
|
||||
import { aciSchema, type AciString } from './ServiceId.std.js';
|
||||
import { signalservice } from '../protobuf/compiled.std.js';
|
||||
|
||||
const { isEqual, isNumber, omit, orderBy, partition } = lodash;
|
||||
|
||||
@@ -869,3 +871,24 @@ export function areBodyRangesEqual(
|
||||
|
||||
return isEqual(normalizedLeft, sortedRight);
|
||||
}
|
||||
|
||||
const bodyRangeOffsetSchema = z.number().int().min(0);
|
||||
const bodyRangeStyleSchema = z.nativeEnum(signalservice.BodyRange.Style);
|
||||
|
||||
export const bodyRangeSchema = z.union([
|
||||
z
|
||||
.object({
|
||||
start: bodyRangeOffsetSchema,
|
||||
length: bodyRangeOffsetSchema,
|
||||
mentionAci: aciSchema,
|
||||
})
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
start: bodyRangeOffsetSchema,
|
||||
length: bodyRangeOffsetSchema,
|
||||
style: bodyRangeStyleSchema,
|
||||
spoilerId: z.number().optional(),
|
||||
})
|
||||
.strict(),
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user