mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-04-17 23:34:14 +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 AciString,
|
||||||
type ServiceIdString,
|
type ServiceIdString,
|
||||||
} from '../../types/ServiceId.std.js';
|
} 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 { PaymentEventKind } from '../../types/Payment.std.js';
|
||||||
import { MessageRequestResponseEvent } from '../../types/MessageRequestResponseEvent.std.js';
|
import { MessageRequestResponseEvent } from '../../types/MessageRequestResponseEvent.std.js';
|
||||||
import type {
|
import type {
|
||||||
@@ -183,6 +186,7 @@ import { ChatFolderType } from '../../types/ChatFolder.std.js';
|
|||||||
import { expiresTooSoonForBackup } from './util/expiration.std.js';
|
import { expiresTooSoonForBackup } from './util/expiration.std.js';
|
||||||
import type { PinnedMessage } from '../../types/PinnedMessage.std.js';
|
import type { PinnedMessage } from '../../types/PinnedMessage.std.js';
|
||||||
import type { ThemeType } from '../../util/preload.preload.js';
|
import type { ThemeType } from '../../util/preload.preload.js';
|
||||||
|
import { safeParseStrict } from '../../util/schemas.std.js';
|
||||||
|
|
||||||
const { isNumber } = lodash;
|
const { isNumber } = lodash;
|
||||||
|
|
||||||
@@ -1675,7 +1679,7 @@ export class BackupExportStream extends Readable {
|
|||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (message.storyReplyContext) {
|
} else if (message.storyReplyContext || message.storyReaction) {
|
||||||
result.directStoryReplyMessage = await this.#toDirectStoryReplyMessage({
|
result.directStoryReplyMessage = await this.#toDirectStoryReplyMessage({
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
@@ -2787,9 +2791,7 @@ export class BackupExportStream extends Readable {
|
|||||||
quote.text != null
|
quote.text != null
|
||||||
? {
|
? {
|
||||||
body: trimBody(quote.text, BACKUP_QUOTE_BODY_LIMIT),
|
body: trimBody(quote.text, BACKUP_QUOTE_BODY_LIMIT),
|
||||||
bodyRanges: quote.bodyRanges?.map(range =>
|
bodyRanges: this.#toBodyRanges(quote.bodyRanges),
|
||||||
this.#toBodyRange(range)
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
attachments: await Promise.all(
|
attachments: await Promise.all(
|
||||||
@@ -2814,22 +2816,45 @@ export class BackupExportStream extends Readable {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#toBodyRange(range: RawBodyRange): Backups.IBodyRange {
|
#toBodyRange(range: RawBodyRange): Backups.IBodyRange | null {
|
||||||
return {
|
const { data: parsedRange, error } = safeParseStrict(
|
||||||
start: range.start,
|
bodyRangeSchema,
|
||||||
length: range.length,
|
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
|
// 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(
|
#getMessageAttachmentFlag(
|
||||||
message: Pick<MessageAttributesType, 'body'>,
|
message: Pick<MessageAttributesType, 'body'>,
|
||||||
attachment: AttachmentType
|
attachment: AttachmentType
|
||||||
@@ -3194,8 +3219,8 @@ export class BackupExportStream extends Readable {
|
|||||||
}> {
|
}> {
|
||||||
const includeLongTextAttachment =
|
const includeLongTextAttachment =
|
||||||
message.bodyAttachment && !isDownloaded(message.bodyAttachment);
|
message.bodyAttachment && !isDownloaded(message.bodyAttachment);
|
||||||
const includeText =
|
const bodyRanges = this.#toBodyRanges(message.bodyRanges);
|
||||||
Boolean(message.body) || Boolean(message.bodyRanges?.length);
|
const includeText = Boolean(message.body) || Boolean(bodyRanges?.length);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
longText:
|
longText:
|
||||||
@@ -3217,9 +3242,7 @@ export class BackupExportStream extends Readable {
|
|||||||
: MAX_BACKUP_MESSAGE_BODY_BYTE_LENGTH
|
: MAX_BACKUP_MESSAGE_BODY_BYTE_LENGTH
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
bodyRanges: message.bodyRanges?.map(range =>
|
bodyRanges,
|
||||||
this.#toBodyRange(range)
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
: undefined,
|
: 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 { MY_STORY_ID } from '../../types/Stories.std.js';
|
||||||
import { generateAttachmentKeys } from '../../AttachmentCrypto.node.js';
|
import { generateAttachmentKeys } from '../../AttachmentCrypto.node.js';
|
||||||
import { itemStorage } from '../../textsecure/Storage.preload.js';
|
import { itemStorage } from '../../textsecure/Storage.preload.js';
|
||||||
|
import { BodyRange } from '../../types/BodyRange.std.js';
|
||||||
|
|
||||||
const CONTACT_A = generateAci();
|
const CONTACT_A = generateAci();
|
||||||
const CONTACT_B = generateAci();
|
const CONTACT_B = generateAci();
|
||||||
@@ -607,7 +608,7 @@ describe('backup/bubble messages', () => {
|
|||||||
await asymmetricRoundtripHarness(
|
await asymmetricRoundtripHarness(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
conversationId: gv1.id,
|
conversationId: contactA.id,
|
||||||
id: generateGuid(),
|
id: generateGuid(),
|
||||||
type: 'incoming',
|
type: 'incoming',
|
||||||
received_at: 3,
|
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 () => {
|
it('drops messages that expire soon', async () => {
|
||||||
await asymmetricRoundtripHarness(
|
await asymmetricRoundtripHarness(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-namespace */
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
|
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
|
import * as z from 'zod';
|
||||||
|
|
||||||
import { SignalService as Proto } from '../protobuf/index.std.js';
|
import { SignalService as Proto } from '../protobuf/index.std.js';
|
||||||
import { createLogger } from '../logging/log.std.js';
|
import { createLogger } from '../logging/log.std.js';
|
||||||
@@ -15,7 +16,8 @@ import {
|
|||||||
SNIPPET_TRUNCATION_PLACEHOLDER,
|
SNIPPET_TRUNCATION_PLACEHOLDER,
|
||||||
} from '../util/search.std.js';
|
} from '../util/search.std.js';
|
||||||
import { assertDev } from '../util/assert.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;
|
const { isEqual, isNumber, omit, orderBy, partition } = lodash;
|
||||||
|
|
||||||
@@ -869,3 +871,24 @@ export function areBodyRangesEqual(
|
|||||||
|
|
||||||
return isEqual(normalizedLeft, sortedRight);
|
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